今天我们来讨论下 Jetpack 中的 ViewModel:
大家都知道 ViewModel 有一个特点就是能够在 Activity 发生重建时做数据的恢复。
我们就针对这个「重建」与「恢复」问一些问题:
- ViewModel 在 Activity 发生旋转等配置发生变化所导致的重建,能恢复数据吗?
- 如果 1 能,尝试从源码角度分析,数据存在哪?怎么存储的?怎么读取的?
- 当 Activity 切换到后台,被系统杀死(进程存活),此时回到 Activity 导致的重建,ViewModel 的数据能恢复吗?为什么?
更多问答 >>
-
每日一问 | 关于 Activity 重建,值得探究的几个问题
2021-08-30 21:37 -
每日一问 | 好奇ActivityThread中为什么会有一个 Application的集合?
2021-08-30 21:36 -
每日一问 | Gson中序列化对象的操作有低侵入的优化方案吗?
2021-12-02 00:50 -
每日一问 UndeclaredThrowableException 是什么异常?
2021-12-02 00:50 -
每日一问 | 我们经常说到的 Android 脱糖指的是什么?
2021-07-11 22:06 -
2021-07-11 22:06
-
每日一问 | Dialog 的构造方法的 context 必须传入 Activity吗?
2021-07-11 22:06 -
2021-05-28 00:29
-
每日一问 | 已经有了 Intent,那为啥还要 PendingIntent?
2021-05-28 00:29
问题1:能够恢复
问题2:先看如何创建ViewModel,在ViewModelProvider的get方法中,先会尝试通过ViewModel的ClassName去获取ViewModel的实例,如果没有则创建并保存在ViewModelStore中。ViewModelStore内部使用HashMap来保存ViewModel
再看如何创建ViewModelStore,ComponentActivity实现了ViewModelStoreOwner,提供getViewModelStore方法。在获取ViewModelStore时,会先从getLastNonConfigurationInstance()恢复,如果没有才新建一个。
viewModelStore何时保存,搜索viewModelStore的赋值语句。在onRetainNonConfigurationInstance方法中
onRetainNonConfigurationInstance方法是覆写于Activity的onRetainNonConfigurationInstance方法,在retainNonConfigurationInstances()方法中被调用。
在ActivityThread中搜索到,performDestroyActivity调用了retainNonConfigurationInstances方法并把数据保存到了ActivityClientRecord的lastNonConfigurationInstances中。在performLaunchActivity中会通过activity的attch方法将lastNonConfigurationInstances又赋值给activity。和上面的getLastNonConfigurationInstance()方法形成闭环。实现了ViewModelStore的保存和恢复。
问题3: 单独使用ViewModel无法恢复,被系统回收时(可使用adb命令来模拟),不会执行onDestroy方法,通过上面的分析可知就无法保存数据,也就无法恢复ViewModelStore。要使能够恢复数据需要ViewModel搭配SavedStateHandle使用。SavedStateHandle内部使用了activity的异常机制,在onSaveInstanceState方法中把数据保存在Bundle中,然后在onCreate去恢复数据,把数据放到新创建viewmodel中。
有一个问题:就是怎么看这个activity 是正常关闭 还是意外关闭。ViewModel 这种数据恢复都是系统异常关闭 系统会保存,那怎么知道要不要保存 这个的判断逻辑在源码中哪块 ...查看更多
有一个问题:就是怎么看这个activity 是正常关闭 还是意外关闭。ViewModel 这种数据恢复都是系统异常关闭 系统会保存,那怎么知道要不要保存 这个的判断逻辑在源码中哪块
在ComponentActivity的构造方法中,会通过Lifecycle观察ON_DESTROY事件,判断是否是Configuration变化来决定清除ViewModelStore。如果是配置变化了 ...查看更多
在ComponentActivity的构造方法中,会通过Lifecycle观察ON_DESTROY事件,判断是否是Configuration变化来决定清除ViewModelStore。如果是配置变化了导致重建Activity,那么不清除ViewModelStore数据,performDestroyActivity就会读取保存数据,从而下次恢复数据。如果是正常Activity销毁,则清除数据,下次就不会恢复。你可以再阅读下SavedStateHandle的源码,它主要是运用onSaveInstanceState机制。
if (!isChangingConfigurations()) { getViewModelStore().clear(); ...查看更多
if (!isChangingConfigurations()) { getViewModelStore().clear(); }
好的 了解
最近又 碰到一个没看懂的问题:有时间的话看能不能给解释一下,感谢 最近在看Broadcast 相关的流程,就发现有一块地方很奇怪:如果我发送的只是一个普通的通知,他会同时加入mParallelBroa ...查看更多
最近又 碰到一个没看懂的问题:有时间的话看能不能给解释一下,感谢 最近在看Broadcast 相关的流程,就发现有一块地方很奇怪:如果我发送的只是一个普通的通知,他会同时加入mParallelBroadcasts 与 mOrderedBroadcasts ,然后之后会执行通知发送。同时加入两个任务列表,会不会发送两边。
第二个问题:mParallelBroadcasts 在BroadcastQueue中是在while循环中,直接全部发送给应用进程,我的疑问是:系统进程一次性发送多条广播,应用进程是怎么处理的。我们都知 ...查看更多
第二个问题:mParallelBroadcasts 在BroadcastQueue中是在while循环中,直接全部发送给应用进程,我的疑问是:系统进程一次性发送多条广播,应用进程是怎么处理的。我们都知道onReceive是在主线程中执行,那么同一时间只能执行一个通知。那么这个并行的意义体现在哪里。
第三个问题:在系统进程发送通知给应用进程的时候,mParallelBroadcasts 循环发送的时候,并没有设置超时限制。发送mOrderedBroadcasts的时候确实设置了超时设置,但是取消的 ...查看更多
第三个问题:在系统进程发送通知给应用进程的时候,mParallelBroadcasts 循环发送的时候,并没有设置超时限制。发送mOrderedBroadcasts的时候确实设置了超时设置,但是取消的地方没怎么看懂,好像还在发送的代码之前
写得很好,学到了,第三个问题的一方面是因为没有走ondestory,另一方面是否也有非配置改变的原因(就算走了ondestory,没有改变配置的情况下也无法恢复) ...查看更多
写得很好,学到了,第三个问题的一方面是因为没有走ondestory,另一方面是否也有非配置改变的原因(就算走了ondestory,没有改变配置的情况下也无法恢复)
原文地址
首先我们来看一下问题一:ViewModel在Activity发生旋转重建时,能恢复数据吗?
举个栗子。
我们创建一个ViewModel,内部管理一个Int型数据,并且用户在界面点击按钮就开始进行自增操作,使用LiveData通知UI更新数据。
UI则只有一个TextView和一个Button,TextView展示数据,Button进行数字自增操作,LiveData对数据进行观察。
准备好后便开始验证:
从上面视频可以看到,当手机横竖屏切换时,Activity会发生重建,但数据并没有重新更新。
所以对于问题一,答案显而易见,ViewModel在Activity发生旋转等配置发生变化所导致的重建,可以恢复数据。
二、原理解析
第二节从案例出发找到了答案,但为什么ViewModel可以恢复数据,数据存在哪?怎么存储的?怎么读取的? 这就要开始去源码里一探究竟。
首先我们想一下,研究的目的是为了了解ViewModel的数据为什么没有被清除掉,ViewModel是保存数据的地方,既然数据没变,那么必然在横竖屏切换时,ViewModel的实例也是没有变的。那么就可以从横竖屏后是怎么获取ViewModel实例的角度来入手,也就是ViewModel的创建。
ViewModel的创建很简单,一共两步,第一步 ViewModelProvider(this) 实例化了ViewModelProvider对象,第二步get() 就是利用该ViewModelProvider根据viewModel类直接拿或者创建ViewModel实例。
创建ViewModel的重点在get()方法里,直接看:
第一句就发现ViewModel对象是从ViewModelStore里根据key拿出来的。接着就开始拿着这个ViewModel对象和开发者传进的viewModel对象进行比较,如果一致,则就直接返回ViewModelStore里的viewModel,反之,则重新创建ViewModel,并且将新ViewModel对象存储到ViewModelStore中。
看到这是不是有点缓存内味,先从缓存里拿数据,如果有数据就返回,如果缓存里没有就重新创建数据,并且将数据put到缓存中。
这里有个类:ViewModelStore ,是ViewModel恢复数据的关键。
ViewModelStore是存储ViewModel的类,内部其实实现了一个HashMap,根据key拿ViewModel实例。在手机横竖屏Activity重建时,因为ViewModelStore之前存储了该ViewModel对象,重建后就直接取出来了。ViewModel的实例没变,数据也就可以再次恢复。
了解了ViewModel对象是存储在ViewModelStore中,那ViewModelStore又是从哪里创建的?
是否还记得在UI层创建ViewModel时分为两步,还有第一步
ViewModelProvider(this)
没有分析,而ViewModelStore的创建就是在这个方法中,直接跟到创建的地方。在ComponentActivity里实现了ViewModelStore的创建。可以看到一开始在mViewModelStore等于null时,则先获取了
NonConfigurationInstances
对象。NonConfigurationInstances
是用来存储ViewModelStore的一个类。当NonConfigurationInstances
对象不为null时,先直接从NonConfigurationInstances
拿ViewModelStore
,如果拿不到则就直接new
一个ViewModelStore
对象。这就是获取ViewModelStore的过程。这里有一个NonConfigurationInstances ,其对象是由调用
getLastNonConfigurationInstance()
获取,而getLastNonConfigurationInstance
所返回的实例是由onRetainNonConfigurationInstance
返回的。首先提一点,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,无法恢复数据。6666666666666
讲的通俗易懂,小白也能看懂
我都看懂了
1、能恢复数据!
2、销毁时存储和重建时读取在:ViewModel 是怎么保存和恢复? (偷个懒~啦啦啦)
3、这种情况不会,ViewMode 旨在配置发现变化时会保存。
最简单方法通过调试查看 ViewModel 实例是否改变,测试后发现这种情况下,ViewModel 是新实例。首先 1 是可以的,使用代码模拟一下就好。
那么为什么 viewmodel 能在 Activity 重建的情况下能保存数据?这一点可以从其实现原理出发。
系统直接从 Framework 层提供了支持,系统配置改变造成的 Activity 实例重建, 但是Framework 层的 ActivityClientRecord 实例不会被销毁重建。
因此,在 Activity 执行 onDestory() 函数之前会创建 NonConfigurationInstances 对象,该对象实例会持有即将销毁的 Activity 的引用,在 ActivityThread 处理即将被销毁的 Activity 的 onDestory() 函数时会将 NonConfigurationInstances 对象保存至 ActivityClientRecord 中。最终,重新创建 Activity 实例时,执行 Activity.attach() 函数时,将其持有的 NonConfigurationInstances 作为参数传入。都直接持有了 Activity 了,那么 Activity 实例中的用来生成/保存 ViewModel 的 ViewModelStore 也是被引用了。
给新创建的 Activity 完成 ViewModelStore 的赋值后,会将之前持有的被销毁的 Activity 实例释放,以待 GC 回收。第三点,其实官方文档保存界面状态文章中已经说明了https://developer.android.google.cn/topic/libraries/architecture/saving-states ,不能!没有持久化到磁盘中 App 被干掉了就没法恢复数据了。
问题1:不存在恢复数据,因为在配置变更场景下不会回收viewmodel
问题2:接问题1的场景,activity在lifeCycle组件检测到destory生命周期时,会去检测是不是配置变更,是就什么都不做,不是则回收viewmodel。问题3: 进程存活,activity被回收,不是配置变更的场景,则是从NonConfigurationInstances中取出之前保存在里面的ViewModelStore,NonConfigurationInstances是保存在ActivityRecord里面的,不会随着activity被回收。NonConfigurationInstances在attach生命周期中被注入