0. onSaveInstanceState & onRestoreInstanceState 的时机
我们经常在网上看到说,onSaveInstanceState 在 Activity 退回到后台,且未来可能被系统杀死时,我们可以在 onSaveInstanceState 中保存一些临时数据,以便在系统真的杀死了进程,并回收了 Activity 后,用户回到 Activity 时,开发者能够在重建的 Activity 的 onCreate 或 onRestoreInstanceState 方法中,能够从 Bundle 中恢复数据。
1. onCreate 和 onRestoreInstanceState 两个回调中都带有恢复数据用的 Bundle 参数,为什么两个地方都可以恢复?
这两个回调的时机不一样。
-
onCreate是在系统构造Activity时被调用的。onCreate中的Bundle如果为空,则表明这是一个新的Activity实例。onCreate中的Bundle如果不为空,则表明这是一个重建的Activity实例。 -
onRestoreInstanceState的调用是在onStart之后,而且只会在Activity被重建时且Bundle不为空时被调用。
2. 那些情况下会被重建?
我们知道在屏幕旋转时 Activity 和 Fragment 会重建,其实还有一种情况会重建,就是我们时常看到博客里说的,当应用在后台时,进程被系统回收,用户再次回到应用时,应用会被重建。
那么应用什么时候会被系统回收呢?我们需要清楚一点,就是系统不会单独地回收 Activity 或者 Fragment,而是会在系统资源不足时,根据应用所在的进程的状态来杀死进程,以回收资源。这里涉及到了一些进程状态的概念:前台进程、可见进程、服务进程 和 缓存进程(process lifecycle)。一般缓存进程会最先被系统回收。
现在的手机 RAM 都非常大,我们怎么模拟这个系统回收进程的过程呢?可以到 开发者选项 -> 应用 -> 后台进程限制,将其从默认的 标准限制 改为 不得超过 1 个进程。这样我们就能够观察到 Activity 或 Fragment 重建时,Bundle 中带有之前调用 onSaveInstanceState 时保存的值了。
3. Fragment 重建时的回调为什么没有 onRestoreInstanceState
Fragement 的数据恢复提供了另外两个回调,onCreate 和 onCreateView,方便开发者在不同的时机恢复数据。
4. Fragment.setRetainInstance 是如何保持 Fragment 的?
Fragment 的 retainInstance 属性默认为 false,当其设置为 true 时,表示 Fragment 实例会在 Activity 因配置变化而重建时,Fragment 自身实例会被保持,不会创建新的实例。它的原理是,调用该方法时,最终调用到了 FragmentManager.addRetainedFragment -> FragmentManagerViewModel.addRetainedFragment,Fragment 实例保存到了 FragmentManagerViewModel.mRetainedFragments 中。mRetainedFragment 是一个以 Fragment 的 uuid(Fragment 自己生成) 为 key,以 Fragment 自身实例为 value 的 HashMap。因此本质上是因为 FragmentManagerViewModel 是一个 ViewModel,它可以在重建周期内保持实例。
retainInstance设置为true之后,Fragment的生命周期会有所变化。由于会保存实例,因此重建时不会再调用onDestroy销毁,也不会再调用onCreate重新实例化。但是onDetach和onAttach依然会调。
5. 什么时候用 onSaveInstanceState,什么时候用 ViewModel,什么时候用 Fragment.setRetainInstance?
-
onSaveInstanceState能够在两种情况下恢复信息:1. 系统配置变化,2. 应用进程被系统回收。由于onSaveInstanceState使用Bundle来将数据序列化到磁盘,因此我们在使用onSaveInstanceState时应当注意:不要存储大量数据,只能存储简单的数据结构及基本类型。因此我们只能存储一些必要的数据,比如用户 ID,当我们重建Activity时,应用可以根据恢复的用户 ID 再次去网络请求 或 查询本地数据库,来获取更多信息以恢复页面。 -
ViewModel保存的数据是在内存中,可以跨越系统配置变化,但是不能在应用进程被系统回收时依然保持数据。ViewModel可以保持更复杂的数据结构。(似乎最近又出了一个SavedStateHandle) -
ViewModel完全可以替代Fragment.setRetainInstance。事实上,ViewModel的内部实现就调用了Fragment.setRetainInstance。
正确的使用姿势应该是,onSaveInstanceState 和 ViewModel 结合使用。
- 当系统回收应用进程后,
onSaveInstanceState中的Bundle不为空,开发者应当将Bundle传给ViewModel。ViewModel发现自己缓存的数据为空,因此使用Bundle中的数据来加载页面。 - 当应用配置改变而重建
Activity时,onSaveInstanceState中的Bundle不为空,开发者应当将Bundle传给ViewModel。由于ViewModel自己有缓存的数据,因此最后由ViewModel自己决定使用缓存的数据还是Bundle中的数据。
5. 怎么样可以使 Activity & Fragment 不再重建?
- 可以通过在 AndroidManifest.xml 中配置
<activity>的android:screenOrientation属性,将Activity的方向固定,可以避免因屏幕旋转导致的重建,同时也不会回调onConfigurationChanged。但是该属性在 多窗口系统 下会失效。 - 通过配置
android:configChanges可以控制在哪些系统配置改变的情况下Activity不重建。最常用的包括orientation,screenSize,keyboardHidden。不过通过该方法,Activity虽然不再重建,但是系统会回调onConfigurationChanged,需要开发者自己处理配置的变换。
6. onRetainCustomNonConfigurationInstance & onRetainNonConfigurationInstance 的作用
我们知道 onSaveInstanceState 中保存的是能够被序列化的数据,Android 系统同样为我们提供了在配置改变时保存没有必要序列化的数据的方法:onRetainCustomNonConfigurationInstance 和 onRetainNonConfigurationInstance,这两个回调方法都返回一个 Object,区别在于,onRetainCustomNonConfigurationInstance 是开放给开发者来保存数据的时机的回调,onRetainNonConfigurationInstance 是 final 方法,用于系统自己保存一些系统资源时使用。
对于 onRetainCustomNonConfigurationInstance 保存的数据,之后我们在重建的 Activity 的 onCreate 方法中,可以通过 getLastCustomNonConfigurationInstance 来直接获得之前保存的数据。这个回调在 Androidx 中已经被标记为 Deprecated,这是因为该机制的职责已经由 ViewModel 代替了。
对于 onRetainNonConfigurationInstance 保存的数据,其实通过阅读源码我们是可以发现,目前的实现就是用来保存了 ViewModel。而 ViewModel 之所以能够在系统配置改变后重建,正是使用了 onRetainNonConfigurationInstance 的恢复机制。