上篇文章提到,虽然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键引起页面意外销毁,然后回到页面,查看日志:
可见当页面意外销毁时,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键触发意外销毁,然后回到页面,查看日志,
发现虽然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
的序列化和反序列化来恢复数据的。
本文使用 mdnice 排版