【Android面试题】ViewModel源码分析,在什么情况下的「销毁重建」能够对数据进行无缝恢复?
一、前言
本篇文章主要涉及以下三个问题:
- ViewModel 在 Activity 发生旋转等配置发生变化所导致的重建,能恢复数据吗?
- 如果 1 能,从源码角度分析,数据存在哪?怎么存储的?怎么读取的?
- 当 Activity 切换到后台,被系统杀死(进程存活),此时回到 Activity 导致的重建,ViewModel 的数据能恢复吗?为什么?
作者
二、案例解答
首先我们来看一下问题一:ViewModel在Activity发生旋转重建时,能恢复数据吗?
举个栗子。
我们创建一个ViewModel,内部管理一个Int型数据,并且用户在界面点击按钮就开始进行自增操作,使用LiveData通知UI更新数据。
class ViewModelTest : ViewModel() {
val countLiveData = MutableLiveData<Int>()
var count: Int = 0
fun addCount() {
count++
countLiveData.postValue(count)
}
}
UI则只有一个TextView和一个Button,TextView展示数据,Button进行数字自增操作,LiveData对数据进行观察。
val viewModel = ViewModelProvider(this).get(ViewModelTest::class.java)
viewModel.countLiveData.observe(this, object : Observer<Int> {
override fun onChanged(t: Int?) {
text.text = t?.toString()
}
})
findViewById<Button>(R.id.bt_add).setOnClickListener {
viewModel.addCount()
}
准备好后便开始验证:
从上面视频可以看到,当手机横竖屏切换时,Activity会发生重建,但数据并没有重新更新。
所以对于问题一,答案显而易见,ViewModel在Activity发生旋转等配置发生变化所导致的重建,可以恢复数据。
三、原理解析
第二节从案例出发找到了答案,但为什么ViewModel可以恢复数据,数据存在哪?怎么存储的?怎么读取的? 这就要开始去源码里一探究竟。
首先我们想一下,研究的目的是为了了解ViewModel的数据为什么没有被清除掉,ViewModel是保存数据的地方,既然数据没变,那么必然在横竖屏切换时,ViewModel的实例也是没有变的。那么就可以从横竖屏后是怎么获取ViewModel实例的角度来入手,也就是ViewModel的创建。
val viewModel = ViewModelProvider(this).get(ViewModelTest::class.java)
ViewModel的创建很简单,一共两步,第一步 ViewModelProvider(this) 实例化了ViewModelProvider对象,第二步get() 就是利用该ViewModelProvider根据viewModel类直接拿或者创建ViewModel实例。
创建ViewModel的重点在get()方法里,直接看:
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
第一句就发现ViewModel对象是从ViewModelStore里根据key拿出来的。接着就开始拿着这个ViewModel对象和开发者传进的viewModel对象进行比较,如果一致,则就直接返回ViewModelStore里的viewModel,反之,则重新创建ViewModel,并且将新ViewModel对象存储到ViewModelStore中。
看到这是不是有点缓存内味,先从缓存里拿数据,如果有数据就返回,如果缓存里没有就重新创建数据,并且将数据put到缓存中。
这里有个类:ViewModelStore ,是ViewModel恢复数据的关键。
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
ViewModelStore是存储ViewModel的类,内部其实实现了一个HashMap,根据key拿ViewModel实例。在手机横竖屏Activity重建时,因为ViewModelStore之前存储了该ViewModel对象,重建后就直接取出来了。ViewModel的实例没变,数据也就可以再次恢复。
了解了ViewModel对象是存储在ViewModelStore中,那ViewModelStore又是从哪里创建的?
是否还记得在UI层创建ViewModel时分为两步,还有第一步ViewModelProvider(this)
没有分析,而ViewModelStore的创建就是在这个方法中,直接跟到创建的地方。
#ComponentActivity
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.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
在ComponentActivity里实现了ViewModelStore的创建。可以看到一开始在mViewModelStore等于null时,则先获取了NonConfigurationInstances
对象。
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
NonConfigurationInstances
是用来存储ViewModelStore的一个类。当NonConfigurationInstances
对象不为null时,先直接从NonConfigurationInstances
拿ViewModelStore
,如果拿不到则就直接new
一个ViewModelStore
对象。这就是获取ViewModelStore的过程。
这里有一个NonConfigurationInstances ,其对象是由调用getLastNonConfigurationInstance()
获取,而getLastNonConfigurationInstance
所返回的实例是由 onRetainNonConfigurationInstance
返回的。
public final Object onRetainNonConfigurationInstance() {
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的触发是在手机横竖屏时,处于onStop和onDestory之间。
从源码里看它的作用就是将viewModelStore存储到NonConfigurationInstances中。 也就是在Activity销毁之前,就把viewModelStore存储起来了
和前面相结合,一切都说得通了。我们再来捋一捋viewModel存储、恢复数据的过程。
-
第一次创建ViewModel时,ViewModelStore利用HashMap将新创建的ViewModel对象存储了起来;
-
在手机横竖屏时,Activity被销毁之前,会触发onRetainNonConfigurationInstance,对ViewModelStore进行存储;
-
Activity重建后,Activity会重新走onCreate生命周期,并且会再次去获取ViewModel对象。而这次ViewModel的获取与第一次创建不同,它会通过ViewModelStoreOwner先获取该Activity重建之前所保存的ViewModelStore,接着在ViewModelStore中根据Key,找到重建之前的ViewModel,进而恢复数据。
四、Activity切换到后台,被系统杀,ViewModel能否恢复数据?
这个问题还是以案例的形式模拟一下。还是第一节的例子,只不过在模拟器设置里将后台进程设置为 ‘不允许后台进程’,当我们将app回到后台后发现,此时并没有触发onRetainNonConfigurationInstance。
我们从上面的原理解析就知道,onRetainNonConfigurationInstance
是Activity销毁前保存ViewModel的关键。被系统杀死,没有触发它,也就无法保存ViewModel,无法恢复数据。
Android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub
文末
您的点赞收藏就是对我最大的鼓励!
欢迎关注我分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!