Jetpack--ViewModel

一、定义

具有宿主生命周期感知能力的数据存储组件,只能感知宿主被销毁的事件,也就是onDestory,此时可以复写onClear方法来做一些清理和释放的工作

ViewModel保存的数据,在页面因配置变更导致页面销毁重建之后依然也是存在的

配置变更:

横竖屏切换;分辨率调整;语言切换;权限变更;系统字体样式变更等
原理:

ViewModel的实例被保存了下来,页面被重建之后,还是获取的同一个ViewModel的实例,就能实现里面数据的复用

正常关闭页面,ViewModel的数据还是会被清理的

如果是电量不足、内存不足等系统原因导致的页面被回收,页面重建之后存储的数据也能再次被复用。

二、用法

1.常规用法

存储的数据,仅仅只能当页面因为配置变更导致的销毁在重建时才可复用,复用的是ViewModel的实例化对象的整体

Kotlin 复制代码
class TestViewModel : ViewModel() {
    val liveData =  MutableLiveData<List<String>>()

    fun loadData(): LiveData<List<String>> {
        //为了适配因配置变更而导致的页面重建,重复利用之前的数据,加快页面渲染,不再请求接口
        if (liveData.value == null){
            val remoteData = queryData()
            liveData.postValue(remoteData)
        }
        return liveData
    }
    
    fun queryData():List<String>{
        //接口请求
        ......
        return data
    }
}

//通过ViewModelProvider来获取TestViewModel的实例
val viewModel = ViewModelProvider(this).get(TestViewModel::class.java)
viewModel.loadData().observe(this,Observer{
      //数据回调
})

2.进阶用法

存储的数据,无论是配置变更,还是因内存、电量不足等系统原因导致的页面被回收再重建都可以复用,即便ViewModel不是同一个实例,它存储的数据也能做到复用,需要用到savedState组件

api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0'

Kotlin 复制代码
//SavedStateHandle 包装了一个hashMap
class TestViewModel(val savedState: SavedStateHandle) : ViewModel() {

    private val KEY_TEST_DATA = "key_test_data"

    val liveData = MutableLiveData<List<String>>()

    fun loadData(): LiveData<List<String>> {
        //内存复用
        if (liveData.value == null) {
            val remoteData = savedState.get<List<String>>(KEY_TEST_DATA)
            liveData.postValue(remoteData)
            return liveData
        }

        //请求接口
        val remoteData = queryData()
        savedState.set(KEY_TEST_DATA, remoteData)
        liveData.postValue(remoteData)
        return liveData
    }

    fun queryData():List<String>{
        //接口请求
        ......
        return data
    }
}

3.跨页面的数据共享

不仅能实现单Activity多Fragment的数据共享,还可以实现跨Activity的数据共享

三、配置变更ViewModel复用实现原理

ViewModel是如何做到在宿主销毁了,还能继续存在,以至于页面恢复重建后还能继续复用

因为获取的是同一个ViewModel实例对象

val viewModel = ViewModelProvider(this).get(TestViewModel::class.java)

Kotlin 复制代码
ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
}
ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory){
    this(owner.getViewModelStore(), factory);
}
ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

在ViewModelProvider需要的是ViewModelStoreOwner,而Activity、Fragment都是实现了ViewModelStoreOwner这个接口的,所以可以直接传递进去

ViewModelStore就是用来存储ViewModel实例的,本质上是一个HashMap

Factory就是用来创建ViewModel实例的

调用get方法,将传递进来的className在拼接一个DEFAULT_KEY字符串,这个key就是ViewModel在ViewModelStore存储的key

Kotlin 复制代码
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

首先根据key来获取ViewModel实例,紧接着看这个实例是不是Class类型,如果是直接返回了,如果不是就根据Factory的类型来创建ViewModel实例,创建完成之后就把它存储到mViewModelStore这个缓存对象中

Kotlin 复制代码
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
        } else {
            viewModel = mFactory.create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

ViewModel的实例就是通过mViewModelStore来获取的,既然想做到ViewModel实例的复用,那就需要mViewModelStore做到复用,mViewModelStore的实例实在ComponentActivity的getViewModelStore()获取的

Kotlin 复制代码
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    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();
            }
        }
    }

NonConfigurationInstances 本质是个包装类,用来包装一些在配置变更后还想继续留存的数据,比如fragment,页面被旋转之后,Activity会被重建,而Fragment还是原来的那个,这个实现原理就是通过NonConfigurationInstances来实现的,ViewModel的复用也是这样

mViewModelStore的存储是在onRetainNonConfigurationInstance中,如果ViewModelStore为空就直接retrun,不为空就构建了NonConfigurationInstances对象,并将ViewModelStore给提取了出来,从而完成实例对象的复用

Kotlin 复制代码
    @Override
    @Nullable
    @SuppressWarnings("deprecation")
    public final Object onRetainNonConfigurationInstance() {
        // Maintain backward compatibility.
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

custom对象是在onRetainCustomNonConfigurationInstance()中得到的

作用:

如果想当配置变更导致被销毁了在Activity中保存一些数据,在Activity在重建时继续复用,此时可以复写onRetainCustomNonConfigurationInstance方法

通过调用getLastNonConfigurationInstance()方法来获取保存的方法

但是这种方式已经被废弃了,推荐使用ViewModel来完成

四、ViewModel的SavedState数据复用原理

SavedState存储的数据即便ViewModel这个实例被销毁了,当Activity被重建的时候也能被复用

涉及到的类:

  • SavedStateHandle的数据存储与恢复,即便ViewModel不是同一个实例,它存储的数据也能做到复用
  • SavedStateRegistryOwner:接口,申明宿主的意思,Activity、Fragment都实现了这个接口
  • SavedStateRegistryController:用于创建SavedStateRegistry,与Activity、Fragment简历联系,剥离SavedStateRegistry与Activity的耦合关系
  • SavedStateRegistry:在实现SavedStateRegistryOwner的同时,要返回SavedStateRegistry对象,作用是:数据存储、恢复中心,每一个ViewModel中的数据都会被收集到这里。一个总Bundle(RestoreState),key-value存储着每个ViewModel对应子Bundle
  • SavedStateHandle:每一个ViewModel对应一个SavedStateHandle,用于存储和恢复数据

SavedState数据存储流程:

逐一调用每个SavedStateHandle保存自己的数据。汇总成一个总的Bundle,在存储到Activity的SavedState对象中

  1. 在内存不足、电量不足等系统元原因导致的Activity页面即将被销毁时,会调用Activity的onSaveInstanceState(Bundle savedState)
  2. 紧接着利用SavedStateRegistryController调用performnSave(Bundle savedState)直接转发
相关推荐
darkb1rd7 分钟前
五、PHP类型转换与类型安全
android·安全·php
gjxDaniel29 分钟前
Kotlin编程语言入门与常见问题
android·开发语言·kotlin
csj5029 分钟前
安卓基础之《(22)—高级控件(4)碎片Fragment》
android
峥嵘life1 小时前
Android16 【CTS】CtsMediaCodecTestCases等一些列Media测试存在Failed项
android·linux·学习
stevenzqzq2 小时前
Compose 中的状态可变性体系
android·compose
似霰2 小时前
Linux timerfd 的基本使用
android·linux·c++
darling3314 小时前
mysql 自动备份以及远程传输脚本,异地备份
android·数据库·mysql·adb
你刷碗4 小时前
基于S32K144 CESc生成随机数
android·java·数据库
TheNextByte15 小时前
Android上的蓝牙文件传输:跨设备无缝共享
android
言之。6 小时前
Kotlin快速入门
android·开发语言·kotlin