首页 > 编程语言 >Android - Jetpack ViewModel源码探秘

Android - Jetpack ViewModel源码探秘

时间:2023-06-22 10:38:16浏览次数:44  
标签:ViewModelStore Jetpack ...... ViewModel vm NonConfigurationInstances 源码 Activity


ViewModel使用场景
  • 当横竖屏切换时,希望数据不丢失,可以用ViewModel当成存储媒介;
  • 可作为Activity & Fragment通讯的媒介;
ViewModel的创建
//Activity中构建MyViewModel
ViewModelProvider(this).get(MyViewModel::class.java)

//ViewModelProviders类中
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());//1
}


//ViewModelStore类中
public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();//2
  • 注释1:构建ViewModelProvider的参数有两个,第一个参数owner.getViewModelStore(),它是vm的存储器,会对vm进行缓存;第二个参数是构建vm的Factory,默认是SavedStateViewModelFactory类型,其内部通过反射构建ViewModel,比较简单就不展开了;
  • 注释2:ViewModelStore用HashMap缓存vm,说到缓存,好像都喜欢用hashmap;

得到ViewModelProvider实例后,通过get获取ViewModel

//ViewModelProvider类中
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    ......
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);//调用下面两个参数的重载函数,传入类名作为key,保证Activity同类型vm只有一个实例
}


//ViewModelProvider类中
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);//1
    if (modelClass.isInstance(viewModel)) {
        ......
        return (T) viewModel;
    } else {
        ......
    }
    ......
    viewModel = mFactory.create(modelClass);//2
    mViewModelStore.put(key, viewModel);//3
    return (T) viewModel;
}
  • 注释1:通过ViewModel存储器来查找,如果有缓存则返回对象;
  • 注释2:工厂构建ViewModel,工厂实现具体可以看SavedStateViewModelFactory;
  • 注释3:缓存vm,与注释1对应;
横竖屏切换,ViewModel如何能做到数据不丢失呢?

只要保证横竖屏切换前后,Activity获取到的ViewModelStore(owner.getViewModelStore())是同一个实例应该就可以了吧。

看下owner.getViewModelStore()巧妙的地方

getViewModelStore()的实现在Activity的父类ComponentActivity中

//ComponentActivity类中
public ViewModelStore getViewModelStore() {
    ......
    ensureViewModelStore();//1
    return mViewModelStore;
}

void ensureViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {//1
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {//2
            mViewModelStore = new ViewModelStore();
        }
    }
}
  • 注释1:getLastNonConfigurationInstance()不为空,则给mViewModelStore赋值,看注释得知NonConfigurationInstances缓存了viewModelStore;
  • 注释2:如果mViewModelStore为空则构建;
  • 到这里我们得知,getLastNonConfigurationInstance是获取缓存viewModelStore入口,对应横竖屏切换后获取vm的场景,那么是否就提供了保存viewModelStore的入口,对应横竖屏切换前保存vm的场景
//ComponentActivity类中
public final Object onRetainNonConfigurationInstance() {
    ......
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}
  • 代码比较简单,构建NonConfigurationInstances,将viewModelStore赋值给内部属性;

onRetainNonConfigurationInstance提供了保存viewModelStore的入口,那么它是在哪里被调用呢,应该不是Activity,因为横竖屏切换Activity对象显然就被销毁了,那么就有必要了解下横竖屏切换的流程。

横竖屏切换的大体流程

ActivityThread.handleRelaunchActivity是横竖屏切换的入口,当时分析销毁恢复的时候有一个误区,以为就是AMS先handleDestroyActivity再handleLaunchActivity,没想到还有一个handleRelaunchActivity函数。

//ActivityThread类中
public void handleRelaunchActivity(ActivityClientRecord tmp,
                                   PendingTransactionActions pendingActions) {
    ......
    handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
            pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");//主要调用handleRelaunchActivityInner
    ......
}

//ActivityThread类中
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
                                         List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
                                         PendingTransactionActions pendingActions, boolean startsNotResumed,
                                         Configuration overrideConfig, String reason) {
    ......
    handleDestroyActivity(r.token, false, configChanges, true, reason);//销毁
    ......
    handleLaunchActivity(r, pendingActions, customIntent);//重建
}
  • 这里的核心是销毁跟重建Activity,猜测handleDestroyActivity对ViewModelStore进行保存,handleLaunchActivity把保存的ViewModelStore再丢给Activity。
看下ActivityThread.handleDestroyActivity销毁的过程
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
                                  boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = performDestroyActivity(token, finishing,
            configChanges, getNonConfigInstance, reason);//调用下面5个参数重载的函数
    ......
}

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
                                            int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);//1
    .....
    r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();//2
    .....
    return r;
}
  • 注释1:根据token获取ActivityClientRecord,这里很关键,跟恢复Activity用是同一个对象;
  • 注释2:这里代码是不是有点熟悉,但还不是最终的代码,跟进去看看
NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();//1;
    ......
    NonConfigurationInstances nci = new NonConfigurationInstances();//2
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
     ......
    return nci;
}
  • 注释1:这里可能有会点绕,得仔细看下,onRetainNonConfigurationInstance函数被ComponentActivity重写了,这里返回的对象类型是ComponentActivity.NonConfigurationInstances,它维护ViewModelStore属性;
  • 注释2:这里的类型是Activity.NonConfigurationInstances,它的activity属性是ComponentActivity.NonConfigurationInstances类型;
  • 所以最终被保存的Activity.NonConfigurationInstances类型对象;
  • 持有关系:Activity.NonConfigurationInstances 持有 ComponentActivity.NonConfigurationInstances对象,后者持有ViewModelStore对象;

类图如下,可以对照源码看下

Android - Jetpack ViewModel源码探秘_java

销毁流程小结

ActivityThread会间接将ViewModelStore保存到ActivityClientRecord当中,以便恢复时使用,注意这里是间接,因为这中间涉及多个数据对象;

看下ActivityThread.handleLaunchActivity恢复的过程
//ActivityThread类中
public Activity handleLaunchActivity(ActivityClientRecord r,
                                     PendingTransactionActions pendingActions, Intent customIntent) {
    ......
    final Activity a = performLaunchActivity(r, customIntent);//调用下面3个参数的重载函数
    ......
    return a;
}

//ActivityThread类中
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        activity.attach(appContext, this, getInstrumentation(), r.token,
                 r.ident, app, r.intent, r.activityInfo, title, r.parent,
                 r.embeddedID, r.lastNonConfigurationInstances, config,
                 r.referrer, r.voiceInteractor, window, r.configCallback);//1
        .....
    return activity;
}

final void attach(Context context, ActivityThread aThread,
                  Instrumentation instr, IBinder token, int ident,
                  Application application, Intent intent, ActivityInfo info,
                  CharSequence title, Activity parent, String id,
                  NonConfigurationInstances lastNonConfigurationInstances,
                  Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                  Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
                  IBinder shareableActivityToken) {
    ......
    mLastNonConfigurationInstances = lastNonConfigurationInstances;//2
    ......
}
  • 注释1:调用attch函数,注意看这个参数r.lastNonConfigurationInstances;
  • 注释2:看到这里是不是就很熟悉了,将横竖屏前保存的lastNonConfigurationInstances取出赋值,注意它里面间接维护了ViewModelStore
恢复流程小结

ActivityThread间接将ViewModelStore通过attach函数再丢给Activity,注意这里是间接,因为这中间涉及多个数据对象;

流程图

注意是间接

Android - Jetpack ViewModel源码探秘_竖屏_02

ViewModel.onCleared何时会被调用
  • Activity销毁(非横竖屏切换)
//ComponentActivity类中
public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    ......
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
    ......
}

构造函数注册监听,当不是因为横竖屏导致的销毁,就会清空ViewModelStore,也就是清空ViewModel;

  • 同类型的vm被get两次,那么第一个vm对象会被清理。
public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();//旧vm被清理
        }
    }
ViewModel & AndroidViewModel区别

后者带Application对象,前者没有。 前面分析的得知,构建vm的工厂是SavedStateViewModelFactory

//ViewModelProvider类中
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ......
    viewModel = mFactory.create(modelClass);//工厂构建vm,工厂是SavedStateViewModelFactory类型
    return (T) viewModel;
}


//SavedStateViewModelFactory类中
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
    ......
    try {
        T viewmodel;
        if (isAndroidViewModel && mApplication != null) {
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
            viewmodel = constructor.newInstance(controller.getHandle());
        }
        return viewmodel;
    } 
    ......
}
  • 如果是vm是AndroidViewModel类型,反射构建vm的时候传入mApplication对象;SavedStateViewModelFactory是在ComponentActivity中构建,构建的时候会对其mApplication对象赋值;
ViewModel & onSaveInstanceState 场景区别
  • 相同的

都可以保存数据;

  • 不同点
  1. 通过onSaveInstanceState保存的数据,最终会到达Binder缓冲区,缓冲区只有1M的空间,所以只能存储轻量级的数据,并且涉及到跨进程通信会额外的开销(序列化、反序列化);
  2. ViewModel的数据是保存在内存中,不会有额外开销;

MVVM 中的VM思考

我们通常会把MVVM的VM跟ViewModel等同,其实我觉得不一定。ViewModel可以当成VM层来使用,但其他满足View跟Model之间数据驱动的、做业务处理的层,都可以是VM层,例如把Presenter改成用LiveData做数据驱动,也可以算是VM。

涉及类
  • ViewModel、AndroidViewModel,均提供数据存储,后者持有Application对象;
  • ViewModeStore,vm仓库,负责vm的缓存以及触发清理;
  • SavedStateViewModelFactory,构建vm的工厂类;
  • ViewModelProvider,vm提供者,负责维护ViewModeStore以及ViewModelProvider.Factory;
  • ActivityClientRecord,横竖屏转换时维护ViewModeStore对象;
总结
  • vm之所以在横竖屏切换后保证数据不丢失,是因为vm的仓库ViewModelStore会被ActivityClientRecord保存,用于恢复后重新赋值,所以也就保证了vm数据不会丢失;
  • Activity销毁(非横竖屏切换)以及同类型的vm被get两次,会触发vm onClear函数。

以上分析有不对的地方,请指出,互相学习,谢谢哦!

标签:ViewModelStore,Jetpack,......,ViewModel,vm,NonConfigurationInstances,源码,Activity
From: https://blog.51cto.com/u_16163452/6534301

相关文章

  • 干了六年Android开发现在裸辞失业了,再过2个月就30了,该怎么继续生活?
    这是我在某论坛看到别人分享的故事,觉得可以展开聊一下,对于我们这些中年程序员,可以裸辞吗?前言首先介绍一下主人公的情况。目前所在的是一家小的创业公司,待了3年多,薪资一般吧,之前在一家中型上市企业也干了三年,因为想涨薪所以跳到现在这家小公司。就在年前,公司年终总结,公司老板会和各......
  • 字节跳动总监封神之作《Android11.0最新Framework解析》,1595页,限时免费下载高清PDF文
    Framework始终穿插在App整个研发生命周期中,不管是从0到1的建立阶段,还是从1到N打磨阶段,都离不开Framework。成为一名AndroidFramework高手,就会成为招聘中非常稀缺的人才,可以成为你的敲门砖。很多同学都表示在面试时必问Framework相关问题。因为目前大公司的app开发都要基......
  • 那些年我面过的「六年经验」的Android初级工程师
    前言不知不觉,作为一个Android团队负责人已经好几年了,在这几年中面试过很多同学,有实习生、有初级的、有中级的、也有高级的。近来回顾以往面试过程,感觉有一些求职者的工作经验和自身能力不匹配,比如六年经验的初级工程师,当然这里只是感觉。不从技术能力来判断,是从一些非技术能力来判......
  • 字节总监用了半个月整理出的1595页《Android11.0 最新Framework解析》高清PDF开发下载
    作为过来人,发现很多学习者和实践者都在AndroidFramework上面临着很多的困扰,比如:工作场景中遇到难题,往往只能靠盲猜和感觉,用临时性的补救措施去掩盖,看似解决了问题,但下次同样的问题又会发作,原因则是缺乏方法论、思路的指引以及工具支持;能力修炼中,缺乏互联网项目这一实践环境,对Fram......
  • Android开发想转行音视频,应该要怎么做?
    在星球经常被问到的问题,Android开发想转行音视频,应该要怎么做?很多人对此都有疑惑,不光有工作多年的职场老司机,也有求学期间的研究生同学们,摘录了其中一部分提问,可以看到大家的疑惑是有类似的。对于星球用户的每个提问我都有认真回答,毕竟每个人的情况不一样,没有什么统一的答案。这些......
  • “入职一年,那个被高薪挖来的Android开发被劝退了。”
    其实,在很多小伙伴的想法中,是希望通过跳槽实现薪酬涨幅,可是跳槽不是冲动后决定,应该谨慎啊~01我的学弟,最近向我吐槽,2020年上半年入职一家公司,当时是高薪挖走的他,所谓钱到位,工作也是充满干劲,不到一年的时间,参与了不少项目。可是疲于应对工作,填了不少的技术“坑”,根本没时间去提升。导......
  • 渣硕Android开发找工作都这么难了吗?千万不要轻易离职......
    坐标北京,21年3月毕业工作,北京某大型互联网码农集散基地渣硕背景。第一份工作在北京的一个80人左右规模的小公司做Android,最近刚刚跳槽成功。做Android是从19年中旬开始,毕业前的第一份工作和第二份工作都在规模不超过20人的小团队练级,毕业前本来有计划留杭州,也拿到不少心仪Offer,但是......
  • 给公司面试了五十多个Android开发,我已经吐血身亡了
    身在某二线互联网公司,面试了很多应聘安卓岗位的程序员。符合要求的很少,目前来看也就百分之五左右。我面试Android的时候一般都是先看看面试者的Java基础知识,然后是一些基本的数据结构和基本的算法。然后是一些面向对象的思想,最后是Android。因为任务基础扎实了,面向对象的思想有了,开......
  • 【金三银四】2022Android面经新鲜出炉啦
    前言春水初盛,垂钓者络绎不绝,鱼儿按捺不住,拍打着尾鳍纷纷跃出水面,沽个好价。本篇真实的记录了我从准备->复习->面试的全过程,分享一些我的真实经验,希望能帮到大家。准备工作开始准备工作之前,首先思考几个问题:如何准备需要复习哪些东西该怎么复习怎么复习最高效职业规划如何准备,从哪......
  • Android Handler消息机制详解
    在Android中,只有主线程才能更新UI,但是主线程不能进行耗时操作,否则会产生ANR异常,所以常常把耗时操作放到其他子线程进行。如果在子线程中需要更新UI,一般都是通过Handler发送消息,主线接收消息后进行相应的UI逻辑处理。一.什么是HandlerHandler是一个消息分发对象。Handler是Andr......