首先希望这篇文章如果对大家有帮助的话,可以点一个善意的赞或者收藏,这对我创作来说非常重要!先感谢大家啦
在上一篇文章中,我们对ViewModel的基本使用以及基本原理与因配置变化的重建过程有了一个基本了解
如果还未看过第一篇文章的盆友可以移步到:https://juejin.cn/post/7224125445272600613 中观看~
上一篇只讲到 ViewModel 当页面因配置变更而重建时会进行复用,但如果是内存不足或者电量不足等系统原因导致的页面被回收时ViewModel是不会被复用的,那这个时候本篇主角SavedState组件就要出场了。
先回顾一下上篇有哪些遗留的问题:
1. 为什么ViewModel能够声明带有Application与SavedStateHandle参数的构造函数?这两类的ViewModel是怎么初始化的
2. 因为内存不足等原因时回收Activity后再重建,是怎么通过SavedState进行恢复数据的?
## 1.ViewModel - SavedState组件介绍
Activity 有着一套 onSaveInstanceState 状态保存机制,旨在页面因 系统原因 被回收时可以保存状态,在页面重建后后可以恢复之前的状态。
但是 ViewModel 是无法直接感知 onSaveInstanceState 被触发的时机的。
于是乎,SavedState 这个中间组件就诞生了,它能够帮助开发者在 ViewModel 中处理 Activity 和 fragment 状态保存和恢复。
在页面即将被销毁的时候,每个使用 SavedState 的 ViewModel 都会创建一个 Bundle 来存储自己的这份数据,最后这些 Bundle 会被汇总到一个 Bundle 中,然后再保存到 onSaveInstanceState(Bundle outState) 的outState 中。
当页面恢复的时候,会从 onCreate(Bundle savedInstanceState) 中的 savedInstanceState 中取出原来存放的总的那个 Bundle,然后再取出一个个的属于 ViewModel 的子 Bundle,于是我们就能在 ViewModel 中复用之前存储的数据了 。
其实就是利用 Bundle 可以保存另一个 Bundle 这么一个特点,分层分区保存数据,让数据之间相互分离,进而方便整存整取。
## 2.如何声明一个带有SaveState的ViewModel
```kotlin
//在构造函数中声明SavedStateHandle
class MyViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {
private val DATA_SAVE_KEY = "save_key";
val data = MutableLiveData<String>()
fun loadData(): MutableLiveData<String> {
if (data.value == null) {
//1.首先先从内存中尝试还原数据
val memoryData = savedStateHandle.get<String>(DATA_SAVE_KEY)
if (memoryData != null) {
data.postValue(memoryData)
}
//2.再从网络中拉取最新数据
val remoteData=fetchDataFormRemote()
//3.再保存到savedState中
savedStateHandle[DATA_SAVE_KEY]=remoteData;
data.postValue(remoteData)
}
return data
}
fun fetchDataFormRemote(): String {
return "从服务器获取的数据";
}
}
```
我们只需要在构造函数中添加SavedStateHandle就使用成功啦,其他什么的额外操作通通不要,可以说简单易用的捏。
我们再看看SavedStateHandle一些核心源码
```kotlin
class SavedStateHandle {
//构造器,恢复的数据会从该参数恢复至regular中
constructor(initialState: Map<String, Any?>) {
regular.putAll(initialState)
}
//内部存储数据用的Map
private val regular = mutableMapOf<String, Any?>()
//在Activity回收时,就会触发该SAM,遍历所有保存的数据到一个Bundle中
private val savedStateProvider =
SavedStateRegistry.SavedStateProvider {
val keySet: Set<String> = regular.keys
val keys: ArrayList<String> = ArrayList(keySet.size)
val value: ArrayList<Any?> = ArrayList(keys.size)
for (key in keySet) {
keys.add(key)
value.add(regular[key])
}
bundleOf(KEYS to keys, VALUES to value)
}
//这里进行了操作符重载,该函数等于 savedStateHandle[key]
operator fun <T> get(key: String): T? {
return regular[key] as T?
}
//这里进行了操作符重载,该函数等于 savedStateHandle[key] = data
operator fun <T> set(key: String, value: T?) {
regular[key] = value
}
}
```
可以看出来,SavedStateHandle只是一个数据容器,他负责了:
1. 存储数据,初始化时外部会输入之前缓存的数据
2. 内部实现了一个SavedStateProvider接口,负责在页面回收时保存数据
在搞清楚SavedStateHandle,接下来的大路就拉开帷幕了,我们要搞清楚以下问题
1. SavedState的组件成员以及各自的职责作用
2. 当页面回收时,整个缓存的过程是怎么样的?
3. 当页面恢复时,是怎么把缓存恢复至对应的ViewModel中?
## 3. SavedState的组件成员介绍
因为谷歌设计ViewModel的时候用了一堆设计模式,所以SavedState会有比较多类,如果穿插着原理中讲解感觉会比较乱,还不如一开始就进行介绍比较清晰。
1. SavedStateRegistryOwner:是一个接口用于获取SavedStatedRegistry对象,Activity实现了该接口
2. SavedStateRegistryController: 内部维护了一个 SavedStatedRegistry,用于服务与连接 Activity\Fragment 和SavedStatedRegistry;
3. SavedStatedRegistry:数据存储、恢复中心;
4. SavedStateHandleController:为每个ViewModel创建出对应的SavedStateHandle,并且从数据中心提出缓存数据恢复其数据;
5. SavedStateHandle:单个 ViewModel 用于数据存储和恢复的地方
再通过以下的UML简单看一下他们的关系,有大概一个概念
(UML图懒得自己画了,在网上找了张没水印的嘻嘻)
![image](http://img1.sycdn.imooc.com/wiki/5ee8307409cd9c2118971359.jpg)
我们需要重点关注的类只有两个,SavedStatedRegistry,另一个是SavedStateHandleController。
上面UML暂时有点抽象也不要紧,后面就会逐一解释,在介绍完成员之后,我们就开始进入一个阶段,当页面回收时,ViewModel是怎么被触发数据的保存的.
## 4.ViewModel缓存数据的过程是怎么样的(上)?
一开始说了,SavedState其实是依赖了Activity的onSaveInstanceState-onRestoreInstanceState状态保存机制,那么,我们就从Activity开始溯源,首先看看Activity有关SavedState的代码
```kotlin
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,//实现了SavedStateRegistryOwner接口
{
//初始化SavedStateRegistryController
final SavedStateRegistryController mSavedStateRegistryController =
SavedStateRegistryController.create(this);
//但是实际上是从mSavedStateRegistryController中获得SavedStateRegistry
public final SavedStateRegistry getSavedStateRegistry() {
return mSavedStateRegistryController.getSavedStateRegistry();
}
//当Framework触发保存时,Activity会通知Controller准备保存
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState);
}
}
```
Activity中没有太多SavedState的逻辑,也确实不应该有,不然Activity会越来越臃肿,所以委托了SavedStateRegistryController去做对应的事,而Activity沦为一个Timing侠,只负责转发.
了解到Activity所做的事之后,我们就去看看SavedStateRegistryController做了什么?
```kotlin
public final class SavedStateRegistryController {
private final SavedStateRegistryOwner owner;
//Controller内部声明了SavedStateRegistry
private final SavedStateRegistry savedStateRegistry;
private SavedStateRegistryController(SavedStateRegistryOwner owner) {
this.owner = owner;
//在构造函数中初始化了SavedStateRegistry
this.savedStateRegistry = new SavedStateRegistry();
}
public final void performSave(Bundle outBundle) {
//调用savedStateRegistry的performSave函数
this.savedStateRegistry.performSave(outBundle);
}
@MainThread
public final void performRestore(Bundle savedState) {
//...
//调用savedStateRegistry的performRestore函数
this.savedStateRegistry.performRestore(savedState);
}
}
```
我们可以看到Controller也只是一个转发层,还有其他小部分逻辑但是为了专注我就去掉了,让大家能比较集中的看到关键地方。
那么Controller既然只是负责转发,那么我们就继续往下钻,看看SavedStateRegistry做了啥,上源码
```kotlin
class SavedStateRegistry internal constructor() {
//...
//这里保存了每个ViewModel的SavedStateHandle,下面会接着说ViewModel是怎么将自身的SavedStateProvider存到这里的,目前先不展开
private val components = SafeIterableMap<String, SavedStateProvider>()
//保存数据
fun performSave(outBundle: Bundle) {
//先新建一个Bundle准备保存数据
val components = Bundle()
//...
//遍历每个ViewModel中的SavedStateHandlea,调用其saveState,将SaveStateHandle中的数据进行打包
val it: Iterator<Map.Entry<String, SavedStateProvider>> =
this.components.iteratorWithAdditions()
while (it.hasNext()) {
val (key, value) = it.next()
components.putBundle(key, value.saveState())
}
//最后将打包好的数据统一保存到outBundle中的一个固定位置,这样就保存好了
if (!components.isEmpty) {
outBundle.putBundle(SAVED_COMPONENTS_KEY, components)
}
}
}
```
所以SavedStateRegistry目前已知的职责就是负责将其下的ViewModel数据的保存,并且是保存到Activity提供的outBunle中
但是有一点还没说的是,components这个Map中的SavedStateProvider,是什么时候保存到SavedStateRegistry中的,这个不急 后面我们会慢慢讲到,我们只需要知道他保存的就是每个ViewModel中的每个SavedState中的SavedStateProvider即可~
接着我们再回顾一下SavedStateHandle
```kotlin
class SavedStateHandle {
//内部存储数据用的Map
private val regular = mutableMapOf<String, Any?>()
//在Activity回收时,就会触发该SAM,遍历所有保存的数据到一个Bundle中
private val savedStateProvider =
SavedStateRegistry.SavedStateProvider {
//SavedStateRegistry最后会触发每个SavedStateHandle中的这个代码块,负责将这个ViewModel中的savedState数据打包成Bundle传出去
val keySet: Set<String> = regular.keys
val keys: ArrayList<String> = ArrayList(keySet.size)
val value: ArrayList<Any?> = ArrayList(keys.size)
for (key in keySet) {
keys.add(key)
value.add(regular[key])
}
bundleOf(KEYS to keys, VALUES to value)
}
}
```
好了,保存的流程基本上讲完了,我们知道了Activity是在什么时机通过Controller转发至Registry,然后Registry再遍历每个SavedStateProvider进行数据保存,总结就是这样!
那我们剩下就两个疑问了
1. 数据是怎么恢复的?
2. Registry中的Provider是什么时候塞进去的?
让我们接着解惑
## 5.数据是怎么恢复的(上)?
```kotlin
public class ComponentActivity extends androidx.core.app.ComponentActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
//在ActivityOnCreate中将有可能上次保存的savedInstanceState回传至RegistryController中
mSavedStateRegistryController.performRestore(savedInstanceState);
//...
}
}
//...RegistryController就不再展开了,它会将savedInstanceState转发至SavedStateRegistry中
class SavedStateRegistry internal constructor() {
//...省略其他代码
//声明一个恢复数据的变量
private var restoredState: Bundle? = null
internal fun performRestore(savedState: Bundle?) {
//简单粗暴的还原!
restoredState = savedState?.getBundle(SAVED_COMPONENTS_KEY)
isRestored = true
}
}
```
没错!简单!粗暴!Activity将上次缓存的Bundle再塞SavedStateRegistry给正儿八经的存放起来!然后光明正大的结束!
What?那ViewModel中的SavedStateHandle中的数据是怎么恢复的?
Good Question!我们接下来会进行最后的解惑,我总结一下目前还没解释的疑问
1. ViewModel是怎么初始化构造函数中带有SavedStateHandle参数的类的?
2. SavedStateRegistry是怎么保存每个ViewModel中的Provider的?
3. 每个ViewModel的SavedStateHandle数据是怎么恢复的?
带着最后的疑问出发!
## 6.数据是怎么恢复的(下)?
最后的一切,我们要重新从ViewModel是怎么创建的进行下手,简单回顾一下创建一个ViewModel的流程
```kotlin
//1.如何创建一个ViewModel
ViewModelProvider(this).get(MyViewModel::class.java)
//2.这里不绕弯子了,Activity中是自带了ViewModelFactory,我们直接展示
public class ComponentActivity extends androidx.core.app.ComponentActivity implements HasDefaultViewModelProviderFactory
//实现了HasDefaultViewModelProviderFactory接口并且返回了SavedStateViewModelFactory
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
}
return mDefaultFactory;
}
}
3.
public open class ViewModelProvider {
private val factory: Factory
//当我们调用这个构造函数的时候,其实他会调用一个defaultFactory方法
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
//喏,如果ViewModelStoreOwner同样也实现了HasDefaultViewModelProviderFactory接口,则获取该接口中返回的factory,也就是第二点中的Activity实际上使用了SavedStateViewModelFactory
internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
if (owner is HasDefaultViewModelProviderFactory)
owner.defaultViewModelProviderFactory else instance
}
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
//...省略部分代码
//按照上面的流程,这里的factory实际上就是SavedStateViewModelFactory
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
```
上面贴了几个类的源码,但是实际上想表达的东西很简单,就是Activity初始化ViewModel是用SavedStateViewModelFactory进行初始化的。接下来全部的疑问,只要看看SavedStateViewModelFactory的源码就统统解开了
```kotlin
//重点还是关注create方法
class SavedStateViewModelFactory : ViewModelProvider.OnRequeryFactory, ViewModelProvider.Factory {
//Application,在我们在ViewModel构造函数中声明application时,就是通过这个传入的
private var application: Application? = null
//保底生产用factory,当ViewModel构造函数上没声明SavedState时则使用这个Factory进行生产
private val factory: ViewModelProvider.Factory
//持有了savedStateRegistry
private var savedStateRegistry: SavedStateRegistry? = null
constructor(application: Application?, owner: SavedStateRegistryOwner) {
savedStateRegistry = owner.savedStateRegistry
this.application = application
//保底Factory会选择默认Factory或是ApplicationFactory
factory = if (application != null) getInstance(application)
else ViewModelProvider.AndroidViewModelFactory()
}
fun <T : ViewModel> create(key: String, modelClass: Class<T>): T {
//判断是否继承了AndroidViewModel
val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
//关键逻辑,这里解开了为什么我们在Viewmodel构造函数中声明Application或者SavedStateHandle的时候,也是能被创建出来,是因为下面会匹配我们的构造器,一旦符合条件则会使用构造器新建对象
val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
//这里不展开贴源码,通过反射判断目标Class类是否包含Application和SavedStateHandle
findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
} else {
//这里判断目标Class类是否只包含了SavedStateHandle
findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
}
// 找不到以上的构造器,那默认是无参ViewModel,走上一篇文章中的初始化方法
if (constructor == null) {
return if (application != null) factory.create(modelClass)
else instance.create(modelClass)
}
//以下逻辑是构造函数中必定含有SavedStateHandle的
//创建出LegacySavedStateHandleController,具体等等贴源码分析,我们目前只需要知道该controller中包含了我们ViewModel所需要的SavedStategHandle,
val controller = LegacySavedStateHandleController.create(
savedStateRegistry, lifecycle, key, defaultArgs
)
//无论是那种构造器,我们都会传入controller中的handle
val viewModel: T = if (isAndroidViewModel && application != null) {
newInstance(modelClass, constructor, application!!, controller.handle)
} else {
newInstance(modelClass, constructor, controller.handle)
}
return viewModel
}
}
```
通过分析SavedStateViewModelFactory源码得知,原来我们在ViewModel上声明构造参数,是会通过匹配构造器反射创建对象的,但是这里又冒出了一个新的类LegacySavedStateHandleController.
只看Factory的源码我们只知道它提供了ViewModel所需的SavedStateHandle,但是它create方法做了什么呢?它有是什么呢?已经到最后的最后了,我们上源码
```kotlin
class LegacySavedStateHandleController {
private LegacySavedStateHandleController() {}
static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle,
String key, Bundle defaultArgs) {
//...省略其他代码
//关键逻辑:从Registry获得恢复的数据!(这个key就是ViewModel的Key)
Bundle restoredState = registry.consumeRestoredStateForKey(key);
//创建ViewModel所需的SavedStateHandle对象,并且将恢复的数据扔进去!
SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
//创建SavedStateHandleController,但是这个SavedStateHandleController我们可以完全先忽略!我们只需要知道他包含了我们最想要的SavedStateHandle即可
SavedStateHandleController controller = new SavedStateHandleController(key, handle);
return controller;
}
}
//最后再回顾一下SavedStateHandle的构造函数
class SavedStateHandle {
//伴生对象,Kotlin语法,理解成静态区即可
companion object {
//创建SavedStateHandle
fun createHandle(restoredState: Bundle?, defaultState: Bundle?): SavedStateHandle {
if (restoredState == null) {
//...省略部分逻辑
//如果缓存数据是空,则直接返回一个空的SavedStateHandleSavedStateHandle
return SavedStateHandle()
}
//开始恢复数据,存储的时候是KeyList和ValueList按序保存的,所以这里也是按Key和Value恢复
val keys: ArrayList<*>? = restoredState.getParcelableArrayList<Parcelable>(KEYS)
val values: ArrayList<*>? = restoredState.getParcelableArrayList<Parcelable>(VALUES)
val state = mutableMapOf<String, Any?>()
//最后将KeyValue对应的恢复到Map中
for (i in keys.indices) {
state[keys[i] as String] = values[i]
}
//将恢复好的数据扔到SavedStateHandl里
return SavedStateHandle(state)
}
}
///省略其他字段...
private val regular = mutableMapOf<String, Any?>()
//构造函数保存缓存的数据
constructor(initialState: Map<String, Any?>) {
regular.putAll(initialState)
}
}
```
总结一下上面代码块说了什么
1. Factory通过LegacySavedStateHandleController中的Create方法做了以下事情
1. 通过Registry之前保存的数据,通过当前正在创建的ViewModel的Key,取出缓存数据
2. 通过SavedStateHandle的createHandle方法,还原出在Bundle中的数据,并且创建出SavedStateHandle对象
3. 然后把创建好并且恢复了缓存的SavedStateHandle对象塞进了一个Controller中(该Controller纯工具人)
4. 然后返回该Controller至Factory中
5. Factory通过构造器创建ViewModel对象,并且将上面Controller中准备好的SavedStateHandle通过构造函数塞进ViewModel中
6. ViewModel Created done~
到这里,ViewModel探索之旅算是告一段落了,本篇交代了以下问题
1. 为什么ViewModel能够声明带有Application与SavedStateHandle参数的构造函数?这两类的ViewModel是怎么初始化的
2. 因为内存不足等原因时回收Activity后再重建,是怎么通过SavedState进行恢复数据的?
## 7.最后可能还有有部分疑问(最后QA环节)
Q:配置变化的重建与回收重建他们数据保存的区别是什么?
> 1. 因为配置变化的数据,最后会被保存到ActivityRecordClient中,但是会在Activity destroy时被销毁
> 2. 因系统问题回收的数据,最后会被保存到ActivityRecord中,那ActivityRecord是什么?这里留个坑,后面我会开启一系列的Framework文章,到时候好奇的童鞋请到我Framework专栏解惑啦哈哈哈
Q.既然用到了Bundle,那Bundle自身的限制是否有效?
> 没错,我们保存的数据不能存超过 1M 的数据。