本篇文章主要涉及以下三个问题:
付十一
首先我们来看一下问题一: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,进而恢复数据。
这个问题还是以案例的形式模拟一下。还是第一节的例子,只不过在模拟器设置里将后台进程设置为 ‘不允许后台进程’,当我们将app回到后台后发现,此时并没有触发onRetainNonConfigurationInstance。
我们从上面的原理解析就知道,onRetainNonConfigurationInstance
是Activity销毁前保存ViewModel的关键。被系统杀死,没有触发它,也就无法保存ViewModel,无法恢复数据。
Android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub
您的点赞收藏就是对我最大的鼓励!
欢迎关注我分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!