我们都知道activity在屏幕旋转的时候会重建,这会导致之前activity的数据丢失。
代码举例:
MyViewModel代码
var index = 0
fun add(callback:(result:Int) -> Unit){
index++
callback.invoke(index)
}
Activity中代码
private val myViewModel by viewModels<MyViewModel>()
val tv = findViewById<TextView>(R.id.tv)
val bt = findViewById<Button>(R.id.bt)
bt.setOnClickListener {
myViewModel.add {
tv.text = "$it"
}
}
这个时候点击操作后触发viewmodel类中add方法,然后index++,然后通过回调函数在activity中更新UI,
当Activity启动的时候TextView显示是0,然后通过点击Button按钮后执行index++操作然后TextView更新UI显示1,这个时候用户旋转屏幕之后我们发现TextView显示结果会重置为0。很多人会想是不是ViewModel没有保存index的数据呢?为了验证ViewModel到底有没有保存数据状态,我又点击Button按钮后TextView更新UI显示的结果是2。这个时候很显然可以确定ViewModel是有保存数据状态,如果你们不相信可以打印一下viewmodel中index的值。那么页面为什么没有更新UI是因为我们是通过回调函数去触发UI刷新的,所以为了能实现activity重建之后页面数据更新,谷歌就推出了LiveData。这个我们下篇文章分析LiveData的原理,这里我们主要分析ViewModel为什么在activity销毁重建之后还能保存数据。下面来看看ViewModel的源码:
首先从创建viewmodel对象方法入手:
第一步
private val myViewModel by viewModels<MyViewModel>()
第二步
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline extrasProducer: (() -> CreationExtras)? = null,
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(
VM::class,
{ viewModelStore },
factoryPromise,
{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
)
}
第三步
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory,
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(
store,
factory,
extrasProducer()
).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
第四步
@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
第五步
@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel!!)
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
// AGP has some desugaring issues associated with compileOnly dependencies so we need to
// fall back to the other create method to keep from crashing.
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
从这里我们可以知道viewmodel对象会通过store[key]获取,如果获取到就返回,没有获取到就创建。
store指的是ViewModelStore,它的作用是存储所有的viewmodel实例。那么我们先看看是怎么获取ViewModelStore实例的。
在ComponentActivity中getViewModelStore方法
第六步
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
第七步
@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
这里我们可以看出获取viewmodelstore首先通过getLastNonConfigurationInstance()获取NonConfigurationInstances实例
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
NonConfigurationInstances实例有两个参数其中一个就是用来保存ViewModelStore实例的。
NonConfigurationInstances是ComponentActivity的内部类,在屏幕销毁重建的时候NonConfigurationInstances实例也会新建。所以就到下面第八步。
第八步
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
这里在屏幕旋转的时候会触发onRetainNonConfigurationInstance方法,然后在方法内部创建局部变量存储viewModelStore保存mViewModelStore,然后因为viewModelStore不等于null,会走到创建NonConfigurationInstances实例,然后给实例赋值。然后在获取ViewModelStore实例的时候会走第七步方法,这里我把代码往下移了方便观看,此时通过ensureViewModelStore方法里面获取getLastNonConfigurationInstance方法会获取到在onRetainNonConfigurationInstance方法创建的NonConfigurationInstances实例,然后通过mViewModelStore = nc.viewModelStore方法获取到被销毁activity的viewModelStore对象。
第七步
```kotlin
@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
我这边在断点调试的时候打印的对象实例
onRetainNonConfigurationInstance方法
ensureViewModelStore方法
所以在屏幕旋转后Activity重建之后ViewModelStore的实力对象是一个,那自然存储在ViewModelStore里面的所有viewmodel都会保存状态。这也就是为什么ViewModel能持久保留界面状态的原因。
综上所述再来回看这几个问题:
ViewModel 类是一种业务逻辑或屏幕级状态容器。
它用于将状态公开给界面,以及封装相关的业务逻辑
1.持久保留界面状态
2.提供对业务逻辑的访问权限