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
的恢复机制。
网友评论