美文网首页
Android-Jetpack笔记-ViewModelSaved

Android-Jetpack笔记-ViewModelSaved

作者: 哈利迪ei | 来源:发表于2020-05-03 19:11 被阅读0次

    上篇文章提到,虽然viewModel要比onSaveInstanceState简单,但是viewModel只能在屏幕旋转和语言切换后(即配置变更时)的页面重建维持数据,当页面意外销毁时数据无法恢复(viewModel也会重建),而这点onSaveInstanceState可以做到。关于意外销毁,我们暂且理解成非配置变更引起的销毁重建,比如内存不足等场景。

    Jetpack笔记代码

    本文源码基于SDK 29

    问题复现

    引入依赖:

    def lifecycle_version = "2.2.0"
    //extensions包含Lifecycles、LiveData、ViewModel
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    

    创建ViewModel

    class CommonViewModel extends ViewModel {
        public MutableLiveData<String> text = new MutableLiveData<>();
    }
    

    在布局文件中使用,

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
            <import type="com.holiday.jetpackstudy.viewmodel_livedata.CommonViewModel" />
            <variable
                name="commonViewModel"
                type="CommonViewModel" />
        </data>
    
        <TextView
              android:id="@+id/tv_text"
              android:text="@{commonViewModel.text}"
              android:textSize="@dimen/tv_text_size" />
    </layout>
    

    在act中使用,

    class ViewModelActivity extends BaseActivity {
        CommonViewModel mCommonViewModel;
        String mTime;
    
        void onCreate(Bundle savedInstanceState) {
            //传this,基于act创建viewModel
            mCommonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
            mBinding.setCommonViewModel(mCommonViewModel);
            //观察数据变化
            mCommonViewModel.text.observe(this, new Observer<String>() {
                @Override
                public void onChanged(String s) {
                    //更新UI
                    mBinding.tvText.setText(s);
                }
            });
            //在页面被意外销毁后,ViewModel会重建
            QrLog.e(String.valueOf(mCommonViewModel.hashCode()));
    
            if (null == savedInstanceState) {
                mTime = String.valueOf(System.currentTimeMillis() / 1000);
                QrLog.e("onCreate 获取当前时间 = " + mTime);
            } else {
                mTime = savedInstanceState.getString("test");
                QrLog.e("onCreate 恢复上次时间 = " + mTime);
            }
        }
    
        void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            //在页面被意外销毁时,存储act的创建时间
            outState.putString("test", mTime);
        }
    }
    

    在onCreate方法中,新加了savedInstanceState的取值操作,同时重写了onSaveInstanceState方法存储时间,那么如何模拟页面被意外销毁呢,可以在开发者选项中选中不保留活动-用户离开后即销毁每个活动,开启后,运行app,然后按home键引起页面意外销毁,然后回到页面,查看日志:

    image

    可见当页面意外销毁时,viewModel并不能很好的维持数据。

    解决

    如果需要让ViewModel能在页面意外销毁时维持数据,那就需要结合SavedStateHandle使用了,新建一个ViewModel

    class SavedStateViewModel extends ViewModel {
        //需要引用SavedStateHandle
        private SavedStateHandle mHandle;
    
        public SavedStateViewModel(SavedStateHandle handle) {
            mHandle = handle;
    
            Object text = mHandle.get("text");
            if (null == text) {
                String time = String.valueOf(System.currentTimeMillis() / 1000);
                mHandle.set("text", time);
                QrLog.e("SavedStateViewModel 初始化数据 = " + time);
            } else {
                QrLog.e("SavedStateViewModel 恢复数据 = " + text);
            }
        }
    }
    

    然后在act中加入:

    //ViewModelActivity.java
    class ViewModelActivity extends BaseActivity {
        SavedStateViewModel mSavedStateViewModel;
        void onCreate(Bundle savedInstanceState) {
            //这边创建时传入了SavedStateViewModelFactory
            mSavedStateViewModel = ViewModelProviders
                .of(this, new SavedStateViewModelFactory(getApplication(), this))
                .get(SavedStateViewModel.class);
            QrLog.e("mSavedStateViewModel hashCode = " + mSavedStateViewModel.hashCode());
        }
    }
    

    运行到该页面,点击home键触发意外销毁,然后回到页面,查看日志,

    image

    发现虽然mSavedStateViewModel不再是同一个实例,但是数据是可以恢复的。

    至于原理,大致的思路就是在SavedStateViewModelFactory中,

    //SavedStateViewModelFactory.java
    <T extends ViewModel> T create(String key, Class<T> modelClass) {
        //借助SavedStateHandleController存储了SavedStateHandle
        SavedStateHandleController controller = SavedStateHandleController.create(
                    mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        //创建viewmodel时传入SavedStateHandle
        T viewmodel = constructor.newInstance(controller.getHandle());
    }
    

    而在SavedStateHandleController中,

    //SavedStateHandleController.java
    static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle,
                                             String key, Bundle defaultArgs) {
        //通过act类名生成的key找到Bundle
        Bundle restoredState = registry.consumeRestoredStateForKey(key);
        //通过Bundle恢复数据,具体实现看下一个方法
        SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
        //包装成SavedStateHandleController进行返回
        SavedStateHandleController controller = new SavedStateHandleController(key, handle);
        return controller;
    }
    
    //SavedStateHandle.java
    static SavedStateHandle createHandle(Bundle restoredState,Bundle defaultState) {
        if (restoredState == null && defaultState == null) {
            return new SavedStateHandle();
        }
        //数据恢复
        Map<String, Object> state = new HashMap<>();
        ArrayList keys = restoredState.getParcelableArrayList(KEYS);
        ArrayList values = restoredState.getParcelableArrayList(VALUES);
        for (int i = 0; i < keys.size(); i++) {
            state.put((String) keys.get(i), values.get(i));
        }
        //虽然SavedStateHandle不再是同一个实例,但是数据都被恢复过来了
        return new SavedStateHandle(state);
    }
    

    即本质还是通过Bundle的序列化和反序列化来恢复数据的。

    参考

    image

    相关文章

      网友评论

          本文标题:Android-Jetpack笔记-ViewModelSaved

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