本文旨在理解ViewModel的设计思想以及本质解决了哪些问题。在阅读本文之前,你需要理解LiveData、Lifecycle。关于ViewModel的使用不在复述直接看官方文档。

  • JetPack | LiveData 如何安全的观察数据
  • JetPack | Lifecycle 如何做到感知生命周期

    ViewModel 是JetPack中一个非常重要的组件,ViewModel 旨在为界面准备数据,管理状态。以生命周期的方式管理界面相关的数据。ViewModel 本质(目标):

    1. ViewModel可以持久化Activity/Fragment/Application作用域依据生命周期持久化数据与清理数据。
    2. ViewModel可以持久化LiveData,通过LiveData解决生命周期有效范围内异步回调问题,防止内存泄漏。
    3. ViewModel在MVVM设计模式中可以隔离Model层和View层。
    4. ViewModel可以实现状态共享。

ViewModel 本质

ViewModel 在Google I/O 大会上很早就推出了包括LiveData、Lifecycle,这三个是一起推出的因为LiveData依赖于Lifecycle,而ViewModel依赖于LiveData。在前两篇文章中Lifecycle是感知生命周期LiveData依赖Lifecycle在活跃的生命周期内发布/订阅数据ViewModel管理界面(View层)的数据和状态。
Lifecycle、LiveData、ViewModel 可以说是三剑客缺一不可。

三剑客的关系:ViewModel持有View层的数据和状态,通过LiveData发布数据,依赖Lifecycle在活跃的生命周期状态分发数据,View层通过ViewModel订阅LiveData,ViewModel状态改变并更新UI。 View层只需要持有ViewModel的引用即可,Model层和View层进行了隔离,ViewModel状态改变,自动更新View层(但是没有DataBinding View层无法自动更新,需要进行单独的设置才可以),这样就形成了MVVM的设计模式(其实在Android三剑客基础上加上DataBinding才是真正的MVVM)。 在ViewModel层,实现LiveData/其他数据在依据作用域内的有效范围内持久化数据。

如下图MVVM的设计实现
image.png

这里是重点: ViewModel 存在的生命周期与传递ViewModelStoreOwner有关。

下面先来看ViewModel是如何状态管理

ViewModel状态管理

创建ViewModel

  1. class RotatedViewModel:ViewModel() {
  2. //ViewModel与LiveData结合 LiveData尽量不要让外部获取到 防止发生不可预期的问题
  3. private val numberData = MutableLiveData<Int>()
  4. public fun observerNumber(owner: LifecycleOwner,observer: Observer<Int>){
  5. /**
  6. * Observe 如之前讲述的LiveData 可以拿到最新的数据 然后调用回调
  7. */
  8. numberData.observe(owner,observer)
  9. }
  10. public fun getNumber():Int{
  11. return numberData.value!!
  12. }
  13. public fun setNumber(value:Int){
  14. numberData.value = value
  15. }
  16. }

Activity 引用ViewModel,并且订阅数据更新,并且观察当屏幕旋转后Activity重建,ViewModel是否会保存状态。

  1. override fun onCreate(savedInstanceState: Bundle?) {
  2. super.onCreate(savedInstanceState)
  3. rotatedViewModel = ViewModelProvider(this).get(RotatedViewModel::class.java)
  4. //屏幕旋转会导致tv重建 需要重新赋值
  5. tv = findViewById(R.id.tv)
  6. //查看屏幕旋转 重建activity后viewmodel是否可以做到数据持久化操作
  7. rotatedViewModel.observerNumber(this){
  8. Log.e("TAG", "onCreate: ${lifecycle.currentState}" )//TAG: onCreate: STARTED RESUMED
  9. tv.text = it.toString()
  10. }
  11. }
  12. override fun onDestroy() {
  13. super.onDestroy()
  14. //屏幕旋转 activity是否会销毁重建 查看viewmodel 是否会重新实例化
  15. Log.e("ViewModelActivity->>>>", "onDestroy: ${hashCode()} viewmodel:${rotatedViewModel.hashCode()}")
  16. }

通过一个按钮点击事件,来更改ViewModel的持有的数据:

  1. fun onChange(view: View) {
  2. rotatedViewModel.setNumber(11)
  3. }

先变更数据旋转屏幕,查看状态:如下图Activity重建了也就是重新初始化了,会重新走onCreate方法,而ViewModel并没有重新实例化,还是获取之前的由于LiveData有粘性事件STARTED状态下异步回调会返回最新的数据,对状态进行了恢复(对textView重新设置值)。
image.png
横屏下状态:
device-2021-08-25-095117.png
似乎看到这里我们可以看懂在ViewModel的官方文档的一张图,ViewMode在Activity的生命周期,在Activity屏幕旋转的状态下ViewModel并不会被销毁,这样ViewModel只要持有Activity的所有数据,都可以进行状态恢复,而不会在通过savedInstanceState保存状态在恢复状态,而ViewModel只需要通过订阅LiveData,当屏幕旋转,Activity的状态为STARTED时订阅数据会通过异步回调返回,根据订阅的异步回调恢复数据。
image.png

看到这里ViewModel完美的解决了Activity的状态问题,通过ViewModel在Activity屏幕发生旋转后不会被销毁持久化数据,然后通过LiveData在活跃的生命周期状态下(STARTEDRESUMED)安全的观察数据,避免内存泄漏的问题。

看到这里我们似乎明白了ViewModel的设计缘由了,简直太棒了,但这还不是ViewModel的全部。

ViewModel作用域

ViewModel作用域可以实现界面间的通信,实现状态共享。 作用域:Activity、Fragment、Application

ViewModel还有一个重要的特性:状态共享。而状态共享和ViewModel的作用域存在重要的关联,而ViewModel的作用域和ViewModelStoreOwner有关。ViewModelStoreOwner 在Activity和Fragment中都进行了实现,外部可能无感知,ViewModelStoreOwner 的实现其实已经封装到了Activity和Fragment中。
image.png

Fragment 之间状态共享-Activity 作用域

在Activity中设置Fragments

  1. //viewmodel 可以在fragment之间互相通信 需要将ViewModel的作用域设置为Activity
  2. val beginTransaction = supportFragmentManager.beginTransaction()
  3. beginTransaction
  4. .add(R.id.fl_master, MasterFragment.newInstance())
  5. .add(R.id.fl_detail, DetailFragment.newInstance())
  6. .commitAllowingStateLoss()

MasterFragment发送数据:ViewModel设置为Activity的ViewModelStoreOwner-此时ViewModel的作用域为Activity

  1. class MasterFragment:Fragment() {
  2. //ViewModel 设置为Activity的ViewModelStoreOwner-
  3. private val model:SharedViewModel by activityViewModels() //ktx 扩展方法
  4. .......
  5. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  6. super.onViewCreated(view, savedInstanceState)
  7. // model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
  8. val btn_master = view.findViewById<Button>(R.id.btn_master)
  9. btn_master.setOnClickListener {
  10. model.select("Master")
  11. }
  12. }
  13. }

DetailFragment 接收数据:ViewModel设置为Activity的ViewModelStoreOwner-此时ViewModel的作用域为Activity

  1. class DetailFragment:Fragment() {
  2. ......
  3. private val model:SharedViewModel by activityViewModels()
  4. ......
  5. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  6. super.onViewCreated(view, savedInstanceState)
  7. val tv_detail = view.findViewById<TextView>(R.id.tv_detail)
  8. // model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
  9. model.selected.observe(viewLifecycleOwner){
  10. tv_detail.text = it
  11. }
  12. }
  13. }

如下图所示:
image.png
看到这里至于Fragment的作用域就不用再介绍了ViewModelProvider(fragment)

Activity 之间状态共享 - Application 作用域

ViewModel 如何进行Activity的状态共享呢?那么就需要将ViewModel的作用域设置为Application。上述讲过作用域和ViewModelStoreOwner有关,也就是需要在Application上实现ViewModelStoreOwner

  1. class App:Application(),ViewModelStoreOwner {
  2. /**
  3. * 存在Application作用域的ViewModel
  4. */
  5. private lateinit var mAppViewModelStore:ViewModelStore
  6. override fun onCreate() {
  7. super.onCreate()
  8. mAppViewModelStore = ViewModelStore()
  9. }
  10. override fun getViewModelStore(): ViewModelStore {
  11. return mAppViewModelStore
  12. }
  13. }

那么Activity设置作用域就是这样的:

  1. //get ViewModelStore获取ViewModel对象如果没有则创建 ViewModel的作用域设置为Application 可以在Activity之间共享状态
  2. rotatedViewModel = ViewModelProvider(this.applicationContext as App).get(RotatedViewModel::class.java)

通过按钮发送数据:

  1. fun onActChange(view: View) {
  2. rotatedViewModel.setNumber(10)
  3. startActivity(Intent(this,TestActivity::class.java))
  4. }

在另一个Activity接收数据:

  1. class TestActivity : AppCompatActivity() {
  2. val tv:TextView by lazy { findViewById(R.id.tv) }
  3. val model:RotatedViewModel by lazy {
  4. ViewModelProvider(this.applicationContext as App).get(RotatedViewModel::class.java)
  5. }
  6. override fun onCreate(savedInstanceState: Bundle?) {
  7. super.onCreate(savedInstanceState)
  8. setContentView(R.layout.activity_test)
  9. model.observerNumber(this){
  10. tv.text = it.toString()
  11. }
  12. }
  13. fun onChange(view: View) {
  14. model.setNumber(100)
  15. }
  16. }

ViewModel的作用域就是通过ViewModelStoreOwner来反映作用域的。同一个 ViewModel 子类,基于不同的作用域,获取到的实例并非同一个。根据传入的 ViewModelStoreOwner 便能拿到符合实际场景所需的 ViewModel 实例。

ViewModel状态保存原理

在Activity的ComponentActivity源码中:
image.png
Activity 初始化ViewModelProvider 传递ViewModelStoreOwner 其实调用的owner.getViewModelStore() 获取ViewModelStore

  1. public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
  2. this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
  3. ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
  4. : NewInstanceFactory.getInstance());
  5. }

ViewModelStore就是一个Map结构用来存储ViewModel

  1. public class ViewModelStore {
  2. private final HashMap<String, ViewModel> mMap = new HashMap<>();
  3. ......
  4. }

而Activity是如何实现ViewModelStoreOwner 呢?如下代码:

注意:通过ensureViewModelStore获取ViewModelStore,这里会看到一个nc的变量,这里先不看它,因为Activity第一次进入nc肯定是null。所以第一次就是返回了一个ViewModelStore的实例

  1. public ViewModelStore getViewModelStore() {
  2. .....
  3. ensureViewModelStore();
  4. return mViewModelStore;
  5. }
  6. void ensureViewModelStore() {
  7. if (mViewModelStore == null) {
  8. NonConfigurationInstances nc =
  9. (NonConfigurationInstances) getLastNonConfigurationInstance();
  10. if (nc != null) {
  11. // Restore the ViewModelStore from NonConfigurationInstances
  12. mViewModelStore = nc.viewModelStore;
  13. }
  14. if (mViewModelStore == null) {
  15. mViewModelStore = new ViewModelStore();
  16. }
  17. }
  18. }

当屏幕旋转Activity重建的时候会调用:onRetainNonConfigurationInstance这个返回最终会返回一个带有viewModelStorenci的实例

  1. public final Object onRetainNonConfigurationInstance() {
  2. Object custom = onRetainCustomNonConfigurationInstance();
  3. ViewModelStore viewModelStore = mViewModelStore;
  4. if (viewModelStore == null) {
  5. NonConfigurationInstances nc =
  6. (NonConfigurationInstances) getLastNonConfigurationInstance();
  7. if (nc != null) {
  8. viewModelStore = nc.viewModelStore;
  9. }
  10. }
  11. if (viewModelStore == null && custom == null) {
  12. return null;
  13. }
  14. //存储viewModelStore
  15. NonConfigurationInstances nci = new NonConfigurationInstances();
  16. nci.custom = custom;
  17. nci.viewModelStore = viewModelStore;
  18. return nci;
  19. }

那么onRetainNonConfigurationInstance是什么时候调用的呢?

onRetainNonConfigurationInstance 方法通过Binder AMS 进程通信调用的。

看Activity的源码如下:当屏幕旋转进程调用retainNonConfigurationInstances并且调用了onRetainNonConfigurationInstance返回的NonConfigurationInstances实例会存储到共享内存中。

  1. NonConfigurationInstances retainNonConfigurationInstances() {
  2. Object activity = onRetainNonConfigurationInstance();
  3. ......
  4. NonConfigurationInstances nci = new NonConfigurationInstances();
  5. nci.activity = activity;
  6. nci.children = children;
  7. nci.fragments = fragments;
  8. nci.loaders = loaders;
  9. if (mVoiceInteractor != null) {
  10. mVoiceInteractor.retainInstance();
  11. nci.voiceInteractor = mVoiceInteractor;
  12. }
  13. return nci;
  14. }

当Activity旋转完毕后,重建的Activity的生命周期会重走onCreate()等,再次调用getViewModelStore会走如下的代码逻辑:

  1. //ComponentActivity : getViewModelStore()
  2. NonConfigurationInstances nc =
  3. (NonConfigurationInstances) getLastNonConfigurationInstance();
  4. if (nc != null) {
  5. // Restore the ViewModelStore from NonConfigurationInstances
  6. mViewModelStore = nc.viewModelStore;
  7. }
  8. //Activity : getLastNonConfigurationInstance会拿到共享内存中存储的NonConfigurationInstances对象,而activity就存储了viewModelStore
  9. NonConfigurationInstances mLastNonConfigurationInstances;
  10. @Nullable
  11. public Object getLastNonConfigurationInstance() {
  12. return mLastNonConfigurationInstances != null
  13. ? mLastNonConfigurationInstances.activity : null;
  14. }

使得当重建时,ViewModelStore 被保留,而在重建后,恢复了 Activity 对该 ViewModelStore 的持有。
并且,对于重建的情况,还会走 isChangingConfigurations的判断,如果为 true,就不走 ViewModelStore 的 clear。在ComponentActivity的构造函数中,监听了onDestory的事件,判断是否是屏幕发生旋转,如果不是会将ViewModelStore进行清空

  1. getLifecycle().addObserver(new LifecycleEventObserver() {
  2. @Override
  3. public void onStateChanged(@NonNull LifecycleOwner source,
  4. @NonNull Lifecycle.Event event) {
  5. if (event == Lifecycle.Event.ON_DESTROY) {
  6. // Clear out the available context
  7. mContextAwareHelper.clearAvailableContext();
  8. // And clear the ViewModelStore
  9. if (!isChangingConfigurations()) {
  10. getViewModelStore().clear();
  11. }
  12. }
  13. }
  14. });

以上就是ViewModel的状态保存原理实现。