JetPack | ViewModel 如何对视图状态管理

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

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

class RotatedViewModel:ViewModel() {
    //ViewModel与LiveData结合 LiveData尽量不要让外部获取到 防止发生不可预期的问题
    private val numberData = MutableLiveData<Int>()

    public fun observerNumber(owner: LifecycleOwner,observer: Observer<Int>){
        /**
         * Observe 如之前讲述的LiveData 可以拿到最新的数据 然后调用回调
         */
         numberData.observe(owner,observer)
    }

    public fun getNumber():Int{
        return numberData.value!!
    }

    public fun setNumber(value:Int){
        numberData.value = value
    }
}
复制代码

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

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

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

    fun onChange(view: View) {
        rotatedViewModel.setNumber(11)
    }
复制代码

先变更数据旋转屏幕,查看状态:如下图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在活跃的生命周期状态下(STARTED RESUMED)安全的观察数据,避免内存泄漏的问题。

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

ViewModel作用域

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

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

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

在Activity中设置Fragments

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

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

class MasterFragment:Fragment() {
    //ViewModel 设置为Activity的ViewModelStoreOwner-
    private val model:SharedViewModel by activityViewModels() //ktx 扩展方法
	.......
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
//        model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
        val btn_master = view.findViewById<Button>(R.id.btn_master)
        btn_master.setOnClickListener {
            model.select("Master")
        }
    }
}
复制代码

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

class DetailFragment:Fragment() {
    ......
    private val model:SharedViewModel by activityViewModels()
    ......
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val tv_detail = view.findViewById<TextView>(R.id.tv_detail)
//        model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
        model.selected.observe(viewLifecycleOwner){
            tv_detail.text = it
        }
    }
}
复制代码

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

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

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

class App:Application(),ViewModelStoreOwner {
    /**
     * 存在Application作用域的ViewModel
     */
    private lateinit var mAppViewModelStore:ViewModelStore

    override fun onCreate() {
        super.onCreate()
        mAppViewModelStore = ViewModelStore()
    }

    override fun getViewModelStore(): ViewModelStore {
        return mAppViewModelStore
    }
}
复制代码

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

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

通过按钮发送数据:

    fun onActChange(view: View) {
        rotatedViewModel.setNumber(10)
        startActivity(Intent(this,TestActivity::class.java))
    }
复制代码

在另一个Activity接收数据:

class TestActivity : AppCompatActivity() {
    val tv:TextView by lazy { findViewById(R.id.tv) }

    val model:RotatedViewModel by lazy {
        ViewModelProvider(this.applicationContext as App).get(RotatedViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        model.observerNumber(this){
            tv.text = it.toString()
        }
    }

    fun onChange(view: View) {
        model.setNumber(100)
    }
}
复制代码

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

ViewModel状态保存原理

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

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
复制代码

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

public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
	......
}
复制代码

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

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

    public ViewModelStore getViewModelStore() {
        .....
        ensureViewModelStore();
        return mViewModelStore;
    }
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
复制代码

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

    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }
        if (viewModelStore == null && custom == null) {
            return null;
        }
        //存储viewModelStore
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
复制代码

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

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

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

NonConfigurationInstances retainNonConfigurationInstances() {
        Object activity = onRetainNonConfigurationInstance();
        ......
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if (mVoiceInteractor != null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;
    }
复制代码

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

//ComponentActivity : getViewModelStore()
NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }

    //Activity : getLastNonConfigurationInstance会拿到共享内存中存储的NonConfigurationInstances对象,而activity就存储了viewModelStore
    NonConfigurationInstances mLastNonConfigurationInstances;
    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
复制代码

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

        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
复制代码

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享