Activity & Fragment 的重建、数据恢复相关问题解惑

0. onSaveInstanceState & onRestoreInstanceState 的时机

我们经常在网上看到说,onSaveInstanceStateActivity 退回到后台,且未来可能被系统杀死时,我们可以在 onSaveInstanceState 中保存一些临时数据,以便在系统真的杀死了进程,并回收了 Activity 后,用户回到 Activity 时,开发者能够在重建的 ActivityonCreateonRestoreInstanceState 方法中,能够从 Bundle 中恢复数据。

1. onCreate 和 onRestoreInstanceState 两个回调中都带有恢复数据用的 Bundle 参数,为什么两个地方都可以恢复?

这两个回调的时机不一样。

  • onCreate 是在系统构造 Activity 时被调用的。onCreate 中的 Bundle 如果为空,则表明这是一个新的 Activity 实例。onCreate 中的 Bundle 如果不为空,则表明这是一个重建的 Activity 实例。
  • onRestoreInstanceState 的调用是在 onStart 之后,而且只会在 Activity 被重建时且 Bundle 不为空时被调用。

2. 那些情况下会被重建?

我们知道在屏幕旋转时 ActivityFragment 会重建,其实还有一种情况会重建,就是我们时常看到博客里说的,当应用在后台时,进程被系统回收,用户再次回到应用时,应用会被重建。

那么应用什么时候会被系统回收呢?我们需要清楚一点,就是系统不会单独地回收 Activity 或者 Fragment,而是会在系统资源不足时,根据应用所在的进程的状态来杀死进程,以回收资源。这里涉及到了一些进程状态的概念:前台进程、可见进程、服务进程 和 缓存进程(process lifecycle)。一般缓存进程会最先被系统回收。

现在的手机 RAM 都非常大,我们怎么模拟这个系统回收进程的过程呢?可以到 开发者选项 -> 应用 -> 后台进程限制,将其从默认的 标准限制 改为 不得超过 1 个进程。这样我们就能够观察到 ActivityFragment 重建时,Bundle 中带有之前调用 onSaveInstanceState 时保存的值了。

3. Fragment 重建时的回调为什么没有 onRestoreInstanceState

Fragement 的数据恢复提供了另外两个回调,onCreateonCreateView,方便开发者在不同的时机恢复数据。

4. Fragment.setRetainInstance 是如何保持 Fragment 的?

FragmentretainInstance 属性默认为 false,当其设置为 true 时,表示 Fragment 实例会在 Activity 因配置变化而重建时,Fragment 自身实例会被保持,不会创建新的实例。它的原理是,调用该方法时,最终调用到了 FragmentManager.addRetainedFragment -> FragmentManagerViewModel.addRetainedFragmentFragment 实例保存到了 FragmentManagerViewModel.mRetainedFragments 中。mRetainedFragment 是一个以 Fragmentuuid(Fragment 自己生成)key,以 Fragment 自身实例为 valueHashMap。因此本质上是因为 FragmentManagerViewModel 是一个 ViewModel,它可以在重建周期内保持实例。

retainInstance 设置为 true 之后,Fragment 的生命周期会有所变化。由于会保存实例,因此重建时不会再调用 onDestroy 销毁,也不会再调用 onCreate 重新实例化。但是 onDetachonAttach 依然会调。

5. 什么时候用 onSaveInstanceState,什么时候用 ViewModel,什么时候用 Fragment.setRetainInstance?

  • onSaveInstanceState 能够在两种情况下恢复信息:1. 系统配置变化,2. 应用进程被系统回收。由于 onSaveInstanceState 使用 Bundle 来将数据序列化到磁盘,因此我们在使用 onSaveInstanceState 时应当注意:不要存储大量数据,只能存储简单的数据结构及基本类型。因此我们只能存储一些必要的数据,比如用户 ID,当我们重建 Activity 时,应用可以根据恢复的用户 ID 再次去网络请求 或 查询本地数据库,来获取更多信息以恢复页面。
  • ViewModel 保存的数据是在内存中,可以跨越系统配置变化,但是不能在应用进程被系统回收时依然保持数据。ViewModel 可以保持更复杂的数据结构。(似乎最近又出了一个 SavedStateHandle
  • ViewModel 完全可以替代 Fragment.setRetainInstance。事实上,ViewModel 的内部实现就调用了 Fragment.setRetainInstance

正确的使用姿势应该是,onSaveInstanceStateViewModel 结合使用。

  • 当系统回收应用进程后,onSaveInstanceState 中的 Bundle 不为空,开发者应当将 Bundle 传给 ViewModelViewModel 发现自己缓存的数据为空,因此使用 Bundle 中的数据来加载页面。
  • 当应用配置改变而重建 Activity 时,onSaveInstanceState 中的 Bundle 不为空,开发者应当将 Bundle 传给 ViewModel。由于 ViewModel 自己有缓存的数据,因此最后由 ViewModel 自己决定使用缓存的数据还是 Bundle 中的数据。

5. 怎么样可以使 Activity & Fragment 不再重建?

  • 可以通过在 AndroidManifest.xml 中配置 <activity>android:screenOrientation 属性,将 Activity 的方向固定,可以避免因屏幕旋转导致的重建,同时也不会回调 onConfigurationChanged。但是该属性在 多窗口系统 下会失效。
  • 通过配置 android:configChanges 可以控制在哪些系统配置改变的情况下 Activity 不重建。最常用的包括 orientationscreenSizekeyboardHidden。不过通过该方法,Activity 虽然不再重建,但是系统会回调 onConfigurationChanged,需要开发者自己处理配置的变换。

6. onRetainCustomNonConfigurationInstance & onRetainNonConfigurationInstance 的作用

我们知道 onSaveInstanceState 中保存的是能够被序列化的数据,Android 系统同样为我们提供了在配置改变时保存没有必要序列化的数据的方法:onRetainCustomNonConfigurationInstanceonRetainNonConfigurationInstance,这两个回调方法都返回一个 Object,区别在于,onRetainCustomNonConfigurationInstance 是开放给开发者来保存数据的时机的回调,onRetainNonConfigurationInstancefinal 方法,用于系统自己保存一些系统资源时使用。
对于 onRetainCustomNonConfigurationInstance 保存的数据,之后我们在重建的 ActivityonCreate 方法中,可以通过 getLastCustomNonConfigurationInstance 来直接获得之前保存的数据。这个回调在 Androidx 中已经被标记为 Deprecated,这是因为该机制的职责已经由 ViewModel 代替了。
对于 onRetainNonConfigurationInstance 保存的数据,其实通过阅读源码我们是可以发现,目前的实现就是用来保存了 ViewModel。而 ViewModel 之所以能够在系统配置改变后重建,正是使用了 onRetainNonConfigurationInstance 的恢复机制。

7. ViewModel 如何解决重建问题的?

ViewModel 的重建恢复原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351