美文网首页
ViewModel实现活动被系统异常销毁时的数据保存与恢复

ViewModel实现活动被系统异常销毁时的数据保存与恢复

作者: 转身搁浅昨天 | 来源:发表于2020-06-07 21:11 被阅读0次

    早期版本的ViewModel仅可实现应用在屏幕旋转等配置发生变化时保存与恢复数据,无法实现Activity在后台时因为内存不足被异常销毁时的数据恢复,也即其不具备类似onSaveInstanceState与onRestoreInstanceState的功能。由于保存活动被异常销毁时的数据的需求很强烈,谷歌在最新的架构组件版本中增加了该功能的支持。

    在2.2.0版本的ViewModel库中,通过SavedStateHandle类完成活动被系统异常销毁时ViewModel数据的保存与恢复。SavedStateHandle内部通过两个Map管理需要恢复的数据(mRegular)与LiveData(mLiveDatas),通过SavedStateHandle获取到的LiveData在设置值时会同时将该值存入到mRegular中。所以为了能够实现数据的存储与恢复,需要创建一个带SavedStateHandle的ViewModel,同时通过该SavedStateHandle获取LiveData。

    在上一篇《ViewModel的原理》中提到2.2.0版本的ViewModel库在创建ViewModel时需要通过创建一个ViewModelProvider的实例来获取ViewModel,当时仅介绍了ViewModelProvider带ViewModelStoreOwner参数的构造函数,其实在ViewModelProvider中还存在如下构造函数

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
            this(owner.getViewModelStore(), factory);
    }
    

    该构造函数可传入一个Factory,实际上ViewModel实例的创建正是通过该Factory接口的如下接口方法创建的:

    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    

    ViewModel库中存在一个实现了Factory接口的SavedStateViewModelFactory类,其create接口方法实现如下:

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
           String canonicalName = modelClass.getCanonicalName();
           if (canonicalName == null) {
                throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
           }
           return create(canonicalName, modelClass);
    }
    
    接口方法内部调用了SavedStateViewModelFactory的接收两个参数的create方法,该方法实现如下: 屏幕快照 2020-06-07 上午10.26.30.png
    private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
                SavedStateHandle.class};
    private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};
    

    在该方法内部会去查找ViewModel带SavedStateHandle参数的构造函数,如果ViewModel为AndroidViewModel,其构造函数中还包含Application参数,如果不存在这样的构造函数,则通过不带SavedStateHandle参数的构造函数实例化ViewModel。若存在这样的构造函数,则先创建SavedStateHandleController,再通过SavedStateHandleController获取SavedStateHandle以创建ViewModel实例,并将该SavedStateHandleController实例保存到ViewModel中以备之后使用。

    通过以上流程我们就可以得到一个带SavedStateHandle参数的ViewModel了,那么SavedStateHandle是如何实现在活动被异常销毁时保存与恢复数据的呢,这就跟SavedStateHandleController、SavedStateRegistryOwner、SavedStateRegistry、SavedStateRegistryController、SavedStateProvider等几个类有关了。

    SavedStateHandleController类用于创建ViewModel绑定的SavedStateHandle对象以及注册SavedStateHandle中的SavedStateProvider至SavedStateRegistry中。

    SavedStateRegistryOwner是一个接口,其拥有SavedStateRegistry,该接口提供一个获取SavedStateRegistry的接口函数。定义如下: 屏幕快照 2020-06-07 下午3.23.43.png

    SavedStateRegistry类管理SavedStateProvider,并对外提供了performSave、performRestore方法进行数据的保存与恢复。

    SavedStateRegistryController类为SavedStateRegistryOwner接口的实现者提供了控制SavedStateRegistry的API方法。

    SavedStateProvider是一个接口,实现该接口的类需要实现其saveState()接口方法,返回一个保存了待恢复数据的Bundle对象。

    AndroidX支持库中的AppCompatActivity继承自FragmentActivity,而后者又继承自ComponentActivity,ComponentAcitivty实现了SavedStateRegistryOwner接口,其实现如下

    @NonNull
    @Override
    public final SavedStateRegistry getSavedStateRegistry() {
            return mSavedStateRegistryController.getSavedStateRegistry();
    }
    

    该方法内部通过SavedStateRegistryController返回了一个SavedStateRegistry对象,SavedStateRegistryController通过单例的方式创建,其在构造函数中创建了一个SavedStateRegistry对象。

    上头在创建ViewModel时是通过SavedStateViewModelFactory工厂类去创建的,该Factory存在如下的构造函数: 屏幕快照 2020-06-07 下午4.34.38.png

    所以在创建SavedStateViewModelFactory时需要传入ComponentActivity,进而获取到SavedStateRegistry。

    SavedStateViewModelFactory的create方法中存在如下逻辑:

    SavedStateHandleController controller = SavedStateHandleController.create(
                    mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    

    其正是通过从ComponentActivity中获取到的SavedStateRegistry创建SavedStateHandleController对象的。

    我们知道活动在异常销毁时是通过onSaveInstanceState方法保存需要恢复的数据的,ViewModel也是通过该方法进行异常销毁时的数据保存,在ComponentActivity中,其onSaveInstatnceState方法如下: 屏幕快照 2020-06-07 下午4.44.41.png 可以看到该方法内部调用SavedStateRegistryController的performSave方法进行数据保存,而SavedStateRegistryController又是通过SavedStateRegistry的performSave方法保存数据,SavedStateRegistry的performSave方法逻辑如下: 屏幕快照 2020-06-07 下午4.47.36.png

    该方法会先判断上次恢复的数据是否都已经被消耗完了,若没有则保存上次恢复的数据中未被消耗完的内容,紧接着循环遍历注册的SavedStateProvider,并保存通过SavedStateProvider的saveState方法返回的数据。

    上头在介绍SavedStateHandleController 时提到其负责SavedStateHandle的创建以及SavedStateProvider的注册,接下来看下其创建SavedStateHandle以及注册SavedStateProvider的逻辑。

    创建SavedStateHandleController的create方法如下: 屏幕快照 2020-06-07 下午4.55.52.png 该方法内部会先通过SavedStateRegistry的consumeRestoredStateForKey方法获取需要恢复的数据,并将需要恢复的数据传给SavedStateHandle的createHandle方法创建SavedStateHandle,SaveStateHandle的createHandle方法会将需要恢复的数据解析出来并保存到它的mRegular这个Map对象中,为之后获取LiveData时恢复数据做准备。创建好SaveStateHandle对象后紧接着创建SavedStateHandleController对象,然后调用SaveStateHandleController的attachToLifeCycle方法,正是在attachToLifeCycle方法内部注册SavedStateProvider到SavedStateRegistry中,其逻辑如下: 屏幕快照 2020-06-07 下午5.19.29.png

    该方法内部通过SavedStateRegistry的registerSavedStateProvider注册SavedStateHandle内部的SaveStateProvider对象到SavedStateRegistry中

    SavedStateHandle中的SavedStateProvider的saveState方法的逻辑如下: 屏幕快照 2020-06-07 下午5.25.45.png

    该方法内部将SavedStateHandle中用于保存数据的mRegular中的所有数据提取出来保存到了Bundle中。

    以上流程实现了ViewModel在活动被系统异常销毁时进行数据的保存,接下来看下ViewModel数据在活动重建时的恢复过程。

    我们知道当活动被异常销毁时,当其重建的时候可通过onCreate方法或者onRestoreInstanceState方法进行数据恢复,在ComponentActivity中,其通过onCreate方法恢复因为异常销毁时保存的ViewModel数据,ComponentActivity的onCreate方法如下: 屏幕快照 2020-06-07 下午5.33.03.png 它通过调用SavedStateRegistryController的performRestore方法恢复数据,该方法内部又调用了SavedStateRegistry的performRestore方法,SavedStateRegistry的performRestore方法如下: 屏幕快照 2020-06-07 下午5.35.32.png 方法内部从Bundle中取出之前通过performSave保存的数据,并保存到mRestoreState变量中,同时设置mRestored标志为true,而SavedStateRegistry恢复ViewModel数据实际是在其consumeRestoredStateForKey方法中实现的,该方法如下: 屏幕快照 2020-06-07 下午5.40.32.png

    该方法内部先判断mRestore标志是否为true,为true时才可进行数据恢复,当mRestoreState不空时,则从其中取出需要恢复的数据,被取出的数据需要进行清除,同时如果mRestoreState中待恢复的数据均被消耗完后,置其为空。

    由之前的介绍可知consumeRestoredStateForKey方法正是在为ViewModel创建SavedStateHandle对象时调用的,当通过SavedStateHandle获取LiveData时,会根据mRegular中是否存在属于该LiveData的待恢复数据,若存在则将该数据取出来创建LiveData,这样便实现了ViewModel中数据的恢复。

    由于ViewModel是通过onSaveInstanceState保存数据的,因为Bundle所能保存的数据大小是有限的,所以不能在ViewModel中存储过大的数据,否则会导致onSaveInstanceState调用出现报错。

    接下来通过一个例子来验证一下通过SavedStateHandle可以实现活动异常销毁时保存与恢复ViewModel中的数据。

    在这个例子中,活动页面中存在一个Button,通过点击Button按钮可改变ViewModel中的数据,ViewModel中的数据是一个MutableLiveData<String>,当数据改变时,页面中会展示数据。在活动的onCreate方法中如下代码用于创建ViewModel以及获取ViewModel中的数据:

    viewModel = new ViewModelProvider(this, new SavedStateViewModelFactory(getApplication(), this)).get(TestViewModel.class);
    viewModel.getModelData().observe(this, new Observer<String>() {
                @Override
                public void onChanged(String s) {
                    appCompatTextView.setText(s);
                }
     });
    

    TestViewModel类的定义如下:

    public class TestViewModel extends ViewModel {
    
        private static final String KEY_MODEL_DATA = "key_model_data";
        private SavedStateHandle mSavedStateHandle;
    
    
        public TestViewModel(SavedStateHandle savedStateHandle) {
            this.mSavedStateHandle = savedStateHandle;
        }
    
        public MutableLiveData<String> getModelData(){
            return mSavedStateHandle.getLiveData(KEY_MODEL_DATA);
        }
    
    }
    

    Button的onClick方法的逻辑如下:

    viewModel.getModelData().setValue("我是ViewModel中保存的数据:" + System.currentTimeMillis());
    

    可以通过在开发者模式中开启“不保留活动”以达到按Home键退出活动时模拟系统异常销毁活动,如果SavedStateHandle可以实现ViewModel数据的保存与恢复,则再次点击应用时,当前活动页面会重建,但页面中会展示按Home键返回到桌面前活动页面上展示的数据,程序运行的情况如下:


    修改ViewModel数据 按Home键退出 点击图标再次打开

    从中间的截图中可看出,按Home键时,活动确实被异常销毁了,因为回调了onSaveInstanceState方法,当再次点击应用图标返回应用时,活动确实进行了重建,并且ViewModel的数据进行了正确恢复。

    相关文章

      网友评论

          本文标题:ViewModel实现活动被系统异常销毁时的数据保存与恢复

          本文链接:https://www.haomeiwen.com/subject/bowqtktx.html