Jetpack--ViewModel

一、定义

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

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

配置变更:

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

原理:

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

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

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

二、用法

1.常规用法

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

class TestViewModel : ViewModel() {
    val liveData =  MutableLiveData>()

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

//SavedStateHandle 包装了一个hashMap
class TestViewModel(val savedState: SavedStateHandle) : ViewModel() {

    private val KEY_TEST_DATA = "key_test_data"

    val liveData = MutableLiveData>()

    fun loadData(): LiveData> {
        //内存复用
        if (liveData.value == null) {
            val remoteData = savedState.get>(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{
        //接口请求
        ……
        return data
    }
}

3.跨页面的数据共享

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

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

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

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

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

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

    @NonNull
    @MainThread
    public  T get(@NonNull Class 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这个缓存对象中

    @NonNull
    @MainThread
    public  T get(@NonNull String key, @NonNull Class 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()获取的

    @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给提取了出来,从而完成实例对象的复用

    @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)直接转发

你可能感兴趣的:(Android:源码篇,Android,android,jetpack)