早期版本的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 create(@NonNull Class modelClass);
ViewModel库中存在一个实现了Factory接口的SavedStateViewModelFactory类,其create接口方法实现如下:
@NonNull
@Override
public T create(@NonNull Class 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方法,该方法实现如下:
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的接口函数。定义如下: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存在如下的构造函数:所以在创建SavedStateViewModelFactory时需要传入ComponentActivity,进而获取到SavedStateRegistry。
SavedStateViewModelFactory的create方法中存在如下逻辑:
SavedStateHandleController controller = SavedStateHandleController.create(
mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
其正是通过从ComponentActivity中获取到的SavedStateRegistry创建SavedStateHandleController对象的。
我们知道活动在异常销毁时是通过onSaveInstanceState方法保存需要恢复的数据的,ViewModel也是通过该方法进行异常销毁时的数据保存,在ComponentActivity中,其onSaveInstatnceState方法如下:该方法会先判断上次恢复的数据是否都已经被消耗完了,若没有则保存上次恢复的数据中未被消耗完的内容,紧接着循环遍历注册的SavedStateProvider,并保存通过SavedStateProvider的saveState方法返回的数据。
上头在介绍SavedStateHandleController 时提到其负责SavedStateHandle的创建以及SavedStateProvider的注册,接下来看下其创建SavedStateHandle以及注册SavedStateProvider的逻辑。
创建SavedStateHandleController的create方法如下:该方法内部通过SavedStateRegistry的registerSavedStateProvider注册SavedStateHandle内部的SaveStateProvider对象到SavedStateRegistry中
该方法内部将SavedStateHandle中用于保存数据的mRegular中的所有数据提取出来保存到了Bundle中。
以上流程实现了ViewModel在活动被系统异常销毁时进行数据的保存,接下来看下ViewModel数据在活动重建时的恢复过程。
我们知道当活动被异常销毁时,当其重建的时候可通过onCreate方法或者onRestoreInstanceState方法进行数据恢复,在ComponentActivity中,其通过onCreate方法恢复因为异常销毁时保存的ViewModel数据,ComponentActivity的onCreate方法如下:该方法内部先判断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
viewModel = new ViewModelProvider(this, new SavedStateViewModelFactory(getApplication(), this)).get(TestViewModel.class);
viewModel.getModelData().observe(this, new Observer() {
@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 getModelData(){
return mSavedStateHandle.getLiveData(KEY_MODEL_DATA);
}
}
Button的onClick方法的逻辑如下:
viewModel.getModelData().setValue("我是ViewModel中保存的数据:" + System.currentTimeMillis());
可以通过在开发者模式中开启“不保留活动”以达到按Home键退出活动时模拟系统异常销毁活动,如果SavedStateHandle可以实现ViewModel数据的保存与恢复,则再次点击应用时,当前活动页面会重建,但页面中会展示按Home键返回到桌面前活动页面上展示的数据,程序运行的情况如下:
从中间的截图中可看出,按Home键时,活动确实被异常销毁了,因为回调了onSaveInstanceState方法,当再次点击应用图标返回应用时,活动确实进行了重建,并且ViewModel的数据进行了正确恢复。