Android Jetpack之ViewModel

avatar
Android @奇舞团Android团队

概述

ViewModel仍然是Model的范畴,是数据对象的载体,但是多了与视图(View)生命周期的绑定关系。可以简单理解为带有生命周期的数据对象。可在Activity, Fragment中使用,保证其在生命周期内的唯一性和一致性,不受配置的更改(例如屏幕旋转)。

使用场景

  • 某页面屏幕旋转导致Activity/ Fragment销毁重建数据不能保存。
    你可能会说我用onSaveInstanceState保存下来,然后某onCreate, onRestoreInstanceState还原啊。onSaveInstanceState设计用来存储那些小的与 UI 相关的并且序列化或者反序列化不复杂的数据。如果被序列化的对象复杂的话,序列化会消耗大量的内存。由于这一过程发生在主线程的配置更改期间,它需要快速处理才不会丢帧和引起视觉上的卡顿。
  • 仍然是屏幕旋转问题,一般页面重建后需要重新请求数据,如果能缓存下来是不是就能减少一次请求?又或者静置后台一小会回来用onSaveInstance实现就略麻烦了。
  • Activity+ Fragments时单个Fragment希望主动拿到Activity的数据完成渲染。此场景换成Activity/ Fragment+自定义View也是一样的。
    你可能又说我让Activity实现个接口,Fragment中将context对象转成接口不就能拿到Activity的数据了嘛。
    我想说还得定义接口,有没有不那么麻烦的方法;另外如果不恰当地调用,Activity被销毁了还会出现空指针啊。
  • 想象某Activity+ Fragments需要Fragment之间通信,甚至某Fragment的更改影响到其他Fragment。
    估计大家的第一反应是让Activity当中介或者用Eventbus这种去通知吧

    总之,学习了ViewModel你会发现一种新思路去解决这些问题。既能保持数据在生命周期内的唯一性又不需要考虑因为异步操作导致的空指针。

ViewModel生命周期

下图可以看出,ViewModel的生命周期和Activity/ Fragment一样甚至更长,因为如果发生屏幕旋转,ViewModel并没有跟着销毁,直到最终的onDestroy。

图1.ViewModel生命周期

ViewModel的使用

1.gradle依赖

dependencies {
    ...
    implementation 'android.arch.lifecycle:extensions:1.1.1'
}

2.ViewModel的声明 千万不能持有Context的引用,否则会引起内存泄漏,如果实在需要Context,可以继承AndroidViewModel,通过getApplication()获取Application。

public class CustomModel extends ViewModel {
      public CustomObj obj;//业务内需要使用的对象
}

3.ViewModel的使用

public class MyActivity extends Activity {
      public void myFunction() {
              //也可以ViewModelProviders.of(MyActivity.this).get("key", CustomModel.class);
              CustomModel customModel = ViewModelProviders.of(MyActivity.this).get(CustomModel.class);
              customModel.obj...//自己操作该对象
      }
}

4.ViewModel与LiveData结合

public class CustomModel extends ViewModel {
      //业务内需要使用的变量
      public MutableLiveData<string> value = new MutableLiveData&lt;&gt;();
}
public class MyFragment1 extends Fragment {
      public void myFunction() {
              CustomModel customModel = ViewModelProviders.of(getActivity()).get(CustomModel.class);
              customModel.value.observe(MyFragment.this, new Observer<string>() {
                    @Override
                    public void onChanged(String value) {
                        //自己操作该参数,如textView.setText(value)
                    }
              });
      }
}
public class MyFragment2 extends Fragment {
      public void myFunction() {
              CustomModel customModel = ViewModelProviders.of(getActivity()).get(CustomModel.class);
              customModel.value.setValue("fragment2 changed the value");//注意postValue
      }
}

这样两个Fragment就可以通信了,LiveData的引入也能实时监听数据的变化反应在界面上。

好了,ViewModel的基本用法掌握了,可以开心地在简历上写熟练掌握Android Jetpack之ViewModel。但是如果在面试官问“你知道它底层是怎么实现的吗”时,你不想一脸懵逼的说我猜。。。可能。。。大概。。。是这样吧,反正作为一个热爱钻研的攻城师的我是看了一遍源码的。

ViewModel实现原理

猜猜怎么实现

1.既然可以存储多个不同的ViewModel,那么它们是怎么存储的?
2.ViewModel数据保存在哪里?Application级别还是Activity/ Fragment级别?
3.怎么保证ViewModel在配置更改(如屏幕旋转)时不被销毁?
4.怎么保证Activity/ Fragment onDestroy时回收?
5.传递的是ViewModel.class,怎么获取实例?
6.AndroidViewModel持有的Application是在哪里设置进去的?

原理分析

下面会一步步分析实现原理,着急的同学请跳到最后直接看答案。 说明:androidx转换前后对于ViewModel存储方式有变化,这里我们分别聊下这两种实现:

androidx转换前

图2.ViewModel的实现类图
(图片来源:blog.csdn.net/zhuzp_blog/…)

  • ViewModel, AndroidViewModel是我们可以继承的类,后者持有Application对象。
  • ViewModelStore是存储ViewModel的类,用HashMap存储。
  • ViewModelProviders提供获取ViewModelProvider的方法,支持activity, Fragment两种传值
  • ViewModelProvider组合VIewModelStore和Factory,可根据ViewModel的名称获取其实例。Factory有默认实现,也可自定义,实例的首次创建就是通过Factory反射得到的。
  • HolderFragment是关键逻辑,实现了ViewModelStore的保存功能。本质上是Activity/ Fragment中插入的空Fragment,能够在配置发生更改时不被销毁。
ViewModelProviders.of(activity/ fragment)

图3.获取ViewModelProvider
图4.检查并得到Application

checkXXX方法校验合法性同时获取上层Context,最终获得Application对象;不自定义Factory的情况下会使用系统的AndroidViewModelFactory,getInstance对象得到static对象,它的作用是在ViewModel实例不存在时创建一个新的。
图5.利用AndroidViewModelFactory创建ViewModel实例

回头看图3ViewModelProvider构造方法(具体见下面图12)的第一个参数是ViewModelStores.of(fragment),原来是为了得到ViewModelStore,我们先看下ViewModelStore的定义。简单来说它是ViewModel的存储工厂,底层用HashMap实现,还对外提供了clear方法。
图6.ViewModelStore类

明白了ViewModelStore是个关键的存储类,那么它怎么获得的?到底存在哪里呢?
图7.ViewModelStores类

先来看下ViewModelStoreOwner是什么,顾名思义是存储ViewModelStore的类,正常Activity/ Fragment一般不存储,那么往下看holderFragmentFor(fragment)就是关键实现了。图8显示该方法是static类型,目的是得到HolderFragment。看来ViewModelStore是存储在HolderFragment中了,那么HolderFragment又是什么东东??
图9.获得HolderFragment

图9.HolderFragment类

原来HolderFragment是一个Fragment,存储着ViewModelStore,在onDestroy的时候调用ViewModelStore.clear方法。注意构造方法中setRetainInstance(true), 这个方法能让Activity在配置更改重建时Fragment不被销毁(具体功能自行查阅),也就是这个功能成为屏幕旋转ViewModel仍然保留的功臣。
图10.HolderFragmentManager类

HolderFragmentManager有俩HashMap分别存Activity与插入的HolderFragment,Fragment和插入的子HolderFragment的键值对,并能监听Activity/ Fragment的生命周期,在destroy时去掉缓存HolderFragment。下图11仍然是HolderFragmentManager的方法,描述了如何创建HolderFragment,如果查找HolderFragment。
图11.HolderFragmentManager类2

回过头和图9中创建HolderFragment联系在一起看,原来是先在当前Activity/ Fragment中查找是否存在HolderFragment,不存在则在HolderFragmentManager的HashMap中找,实在没有就创建一个放在Activity/ Fragment中。得到HolderFragment就能得到ViewModelStore,接着就有了ViewModelProvider,那么接下来怎么得到ViewModel实例呢?

ViewModelProvider.get(ViewModel.class)

图12.获取ViewModel实例

获取实例的方法很简单,从ViewModelStore中查找,有则直接返回,没有则通过反射创建一个(mFactory的创建过程见AndroidViewModelFactory)。至此,我们就找到了我们的主角ViewModel。
总结思想就是在Activity/ Fragment中插入一个唯一的HolderFragment来保存ViewModelStore,HolderFragment没有视图信息,不加入到view container中,通过setRetainInstance(true)保证配置更改不被销毁,但是在onDestroy时手动调用ViewModelStore.clear方法释放掉Map中所有的ViewModel对象。由于没有主动从载体中清除,HolderFragment本身就能和外层载体生命周期一样长,同时setRetainInstance(true)保证了即使屏幕旋转后载体重建也能不被销毁,也可以说它的生命周期比载体还长,从而保证了HolderFragment中的ViewModelStore在整个载体存续期间的唯一性和一致性,间接保证了ViewModel的唯一。
Amazing, 想明白了吗?这么看来onRetainInstance挺好用的,但是总觉得承载体都没有了,真的不会内存泄漏嘛?首先官方的东西一般不会泄漏的,另外我简单分析了一下:
1.没有View试图(其实有也没关系,配置更改时View会被释放重建的)
2.ViewModel中不持有Context对象
3.在配置更改时承载体消失只是临时的,很快又重建并attach,如果是长时间地承载体消失,那么它也会慢慢被垃圾回收的。

androidx转换后

1.不变:ViewModel, AndroidViewModel, ViewModelStore, ViewModelProvider, Factory
2.变化:ViewModelStores废弃,HolderFragment删除。Activity/ Fragment实现了ViewModelStoreOwner类, 也就是ViewModelStore存在于Activity/ Fragment载体自身。
源码的差异点是获取ViewModelStore的方式如图13。不同于HolderFragment统一处理,这种实现中Activity/ Fragment保存ViewModelStore的方式不相同,下面我们分开说明。

图13.获取ViewModelStore

FragmentActivity中ViewModelStore

关键点是onRetainNonConfigurationInstance(), getLastNonConfigurationInstance(), 至于实现原理自行查阅。

图14.FragmentActivity之getViewModelStore()

如果载体真的发生配置更改,ViewModelStore则先保存到NonConfigurationInstances中,然后在OnCreate时再进行还原如图15,图14中再获取一遍也没什么。留意下面fragments.restoreAllState则是对Fragment中ViewModelStore的还原,我们后面再说。这里的NonConfigurationInstances在哪里设置进去的呢?
图15.onCreate中还原ViewModelStore

图16.onRetainNonConfigurationInstance

上图16展示了NonConfigurationInstances的保存,不同于onSaveInstance数据格式是Bundle,这个方法允许保存对象。存储内容分三块用户自定义对象,ViewModelStore对象和FragmentManagerNonConfig对象。对于用户自定义部分看注释发现通过重写onRetainCustomNonConfigurationInstance()实现,ViewModelStore是我们今天的主角,最后一个FragmentManagerNonConfig则是Fragment中数据的存储,包含了Fragment中ViewModelStore逻辑。留意下mFragments.retainNestedNonConfig()是保存Fragment的数据,我们后面说。
那么ViewModelStore是怎么在Activity.onDestroy()时销毁呢,源码中说只有因为配置更改导致Activity destroy时ViewModelStore是不回调clear方法的。
图17.Activity之onDestroy

onRetainNonConfigurationInstance的调用时机是介于onStop和onDestroy之间,在onSaveInstance之后。
总结,ViewModelStore存在于FragmentActivity中,只是在配置更改导致Activity销毁前时通过onRetainNonConfigurationInstance将数据对象保存,并在重建时通过getLastNonConfigurationInstance将数据对象还原回来,从而保证了Activity内ViewModelStore的唯一性和一致性。

Fragment中ViewModelStore

Fragment中实现保留机制大体相同,只是数据存储在FragmentManagerNonConfig中,下图18说明了当配置更改时Activity内需要存储的数据,包括三部分:不被销毁的Fragment集合(通过setRetainInstance(true)),所有子Fragment中需要保存的数据以及所有的Fragment中的ViewModelStore

图18.FragmentManagerNonConfig类

图19.Fragment之getViewModelStore

上图中直接返回mViewModelStore对象,比FragmentActivity少了一步还原过程,由于它没有getLastNonConfigurationInstance,所以只能在FragmentActivity的onCreate中主动调用FragmentController.restoreAllState来还原Fragment相关数据(入口见图16),下图20简单得展示了FragmentManager.restoreAllState方法还原ViewModelStore的过程
图20.FragmentManager中还原ViewModelStore

Fragment中ViewModelStore的还原过程了解了,但是保存又是什么时候完成的呢? 还记得图16中FragmentActivity的OnRetainNonConfigurationInstance()时调用FragmentController.retainNestedNonConfig()吗,这个函数的作用就是重建Activity时重建或者还原Fragment。图21描述了将fragment.mRetaining设置为true的过程(注意mRetaining和mRetainInstance不一样哦,前者只是在当前配置更改时不销毁),根据注释可以看出mSavedNonConfig是在这个之前通过saveNonConfig设置的,其实际调用顺序是 FragmentActivity.onSaveInstanceState()-> FragmentManager.saveAllState()-> FragmentManager.saveNonConfig()
图21.FragmentManager.retainNonConfig

图22.FragmentManager.saveNonConfig

上图保存的数据包括三部分:通过setRetainInstance(true)需要被保存的Fragment实例,子Fragment中的保存数据以及所有Fragment中不为空的ViewModelStore对象。它们最终会在FragmentActivity.OnRetainNonConfigurationInstance时作为和FragmentActivity中的ViewModelStore同级别的对象一起被缓存起来,直到Activity重建onCreate中被还原。
最后onDestroy时ViewModelStore.clear逻辑与FragmentActivity实现大同小异如图23.
图23.Fragment之onDestroy

说到这里,Activity/ Fragment中ViewModelStore的保存和还原就介绍完了。晕乎乎的,怎么就冒出来一个onRetainNonConfigurationInstance,其实这个方法很早就存在,可以缓存任何你想缓存的对象,哪怕是activity实例,AnsycTask都行,这个方法用于配置更改导致Activity重建后的对象的还原。

原理总结

在android和androidx下ViewModel的存储方式完全不同,但是暴露给我们使用的方法却是不变的,这也说明架构的用心。当前恰逢android和androidx的交接,具体以哪个为准我不确定。 上面的问题1, 5, 6的实现方式一致,在此先给出答案:
1.多个ViewModel存储在ViewModelStore中,本质上是一个Map存储的键值对。key为ViewModel的名字或者get方法时自己设置的key。
5.先查找ViewModelStore中是否含有该实例,有则直接返回,没有则通过反射的方式得到实例,并加入ViewModelStore中。
6.ViewModelProviders.of(activity/fragment),通过activity/ fragment拿到Application,当然AndroidViewModel也是通过反射拿到初始实例的,和ViewModel不同的是它需要将application传过去。

  • android的support包:
    2.ViewModel本质上属于Activity/ Fragment级别,通过FragmentManager/ ChildFragmentManager插入一个没有View的Fragment-HolderFragment,ViewModelStore即保存在HolderFragment中。
    3.在onCreate时setRetainInstance(true), 保证屏幕旋转时fragment不被销毁。
    4.HolderFragment在onDestroy时回调ViewModelStore.clear,循环调用viewModel.onCleared.
  • androidx包:
    2.ViewModel仍然属于Activity/ Fragment级别,只是ViewModelStore是分别存储在FragmentActivity和Fragment中
    3.在Activity销毁重建过程是通过onRetainNonConfigurationInstance(), getLastNonConfigurationInstance()保存和还原的ViewModelStore对象的;
    Fragment略微复杂,在调用FragmentActivity onStop和onDestroy之间onSaveInstanceState之后saveAllState的saveNonConfig时缓存FragmentManagerNonConfig对象,里面存储的数据包括不被销毁的fragment集合,循环嵌套的子Fragment的FragmentManagerNonConfig集合以及所有Fragment的ViewModelStore集合,它们在FragmentActivity重建onCreate时还原每个Fragment的ViewModelStore对象,可以说ViewModelStore的存储介质是正常被销毁的,只是ViewModelStore数据被保存并在特定场景下还原回来,保存和还原都是由FragmentActivity主动触发的;
    3.如果是由配置更改导致Activity/ Fragment onDestroy则不清除ViewModelStore的数据,反之则清除。
    总之,不管采用不销毁ViewModelStore的存储介质,还是采用保存再还原数据的方式,都能保证在当前作用域下数据的唯一性和完整性。代码说起来复杂,但是设计思想值得我们去消化学习。

关注微信公众号,最新技术干货实时推送

image