Appearance
Android 生命周期总览
本篇覆盖声明:聚焦 Activity / Fragment / ViewModel / SavedStateHandle / 进程恢复 / 前后台切换 / 配置变更 / 冷启动、温启动、热启动。重点不是背回调名,而是建立 “宿主实例、UI 树、状态容器、进程存活、系统调度” 五条线一起看 的工程判断。
一句话定义
Android 生命周期本质上是在回答两件事:系统现在允许你的页面做什么,以及 你的状态应该活到哪一层。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| Activity 基础生命周期顺序(Kotlin + XML) | android-activity-lifecycle-basic | LifecycleLogActivity.kt · ActivityDetailActivity.kt |
| Activity 生命周期 + Compose 组合树进入/退出 | android-activity-lifecycle-compose | ComposeLifecycleActivity.kt |
| Activity 基础生命周期顺序(Java + XML) | android-activity-lifecycle-java-xml | JavaLifecycleActivity.java |
推荐学习顺序
- 先把 冷启动 / 温启动 / 热启动 三种入口与
onCreate/onStart/onResume的关系吃透。 - 再看 Activity / Fragment 可见性与交互性,建立“谁先
onPause、谁后onStop”的时序感。 - 再看 ViewModel / SavedStateHandle / 持久化 三层状态分工。
- 最后看 配置变更、前后台切换、进程恢复,把线上最常见的状态丢失、重复请求、重复埋点串起来。
为什么需要
Android 生命周期问题几乎都不是“不会 API”,而是 状态放错层、工作挂错时机、错误假设进程一定活着:
- 详情页返回后为什么列表页又请求了一次
- 一句话答:常因误把数据加载写在
onResume()或未区分首次进入与回到前台,导致页面每次获取焦点都重复触发网络调用。(详见下文 §4.1、§5及复习题 6)
- 一句话答:常因误把数据加载写在
- 横竖屏切换后为什么输入框内容没了
- 一句话答:配置变更导致 Activity 销毁重建,若状态仅存放在内存字段中且控件未设置
android:id或未接入SavedStateHandle,状态将随旧实例一同丢失。(详见下文 §1、§4.2及复习题 5)
- 一句话答:配置变更导致 Activity 销毁重建,若状态仅存放在内存字段中且控件未设置
- 退后台回来为什么页面没重建,但轮询恢复了两次
- 一句话答:退后台切回触发
onResume()时未对订阅或计时器做幂等保护或销毁时未清理,导致新旧定时任务在同一个 Activity 实例中重复挂载。(详见下文 §4.1及复习题 6)
- 一句话答:退后台切回触发
- 系统回收进程后,为什么
ViewModel没了但SavedStateHandle还能恢复一部分状态- 一句话答:
ViewModel随 Activity 宿主 Store 存于进程内存中,进程终止即清空;而SavedStateHandle会序列化交由系统进程暂存,重启后由SavedStateRegistry恢复。(详见下文 §1、§4.3及复习题 2)
- 一句话答:
- Fragment 放进
ViewPager2/ Navigation 后,为什么onResume不再等于“真正可见”- 一句话答:引入
setMaxLifecycle()后,不可见 Fragment 也可能进入RESUMED状态,页面真正可见需结合userVisibleHint或生命周期事件监听控制。(详见下文 §1、§3)
- 一句话答:引入
- 冷启动、从最近任务恢复、点击通知唤醒,这三种看起来都叫“打开 App”,但排障路径完全不同
- 一句话答:冷启动包含完整的 Application 进程创建和框架类加载;最近任务恢复依赖进程存活与 SavedState 序列化;通知唤醒通常带有特殊 LaunchMode 或路由 Intent 参数。(详见下文 §1、§4.4及复习题 4)
对一个 10 年 Android / Flutter 工程师来说,真正重要的是:
- 知道什么状态该放哪层
- 知道什么回调会重复、什么回调不保证执行
- 知道系统回收、配置变更、宿主重建时哪些对象会活下来
- 能把 Android 的这套经验迁移到 Flutter 宿主生命周期与页面恢复模型
底层机制
1. 先把五条线分开:实例、可见性、状态、进程、任务栈
Android 生命周期之所以容易讲乱,是因为很多人把下面几件事混成一件:
| 维度 | 你真正该问的问题 | 典型载体 |
|---|---|---|
| 宿主实例 | 这个 Activity/Fragment 实例还在不在? | onCreate/onDestroy |
| 可见性 | 用户看不看得到? | onStart/onStop |
| 可交互性 | 用户现在能不能点? | onResume/onPause |
| 状态容器 | 这份状态该跨什么边界存活? | 字段 / ViewModel / SavedStateHandle / DB |
| 进程存活 | 整个 app 进程还活着吗? | LMK、recent task、系统恢复 |
一句话记忆:
- Activity/Fragment 生命周期 管的是宿主与 UI 附着
- ViewModel 管的是“同一逻辑页面重建时还想保留的内存态”
- SavedStateHandle /
onSaveInstanceState管的是“系统帮你短期记住一点可恢复状态” - 数据库 / DataStore / 文件 管的是“进程没了也要回来”
2. Activity 生命周期:关注“可见”和“可交互”是两件事
最常见顺序:
text
冷启动进入页面:
onCreate
→ onStart
→ onResume
打开下一个全屏 Activity:
当前页 onPause
→ 新页 onCreate
→ 新页 onStart
→ 新页 onResume
→ 当前页 onStop
返回上一页:
当前详情页 onPause
→ 上一页 onRestart
→ 上一页 onStart
→ 上一页 onResume
→ 详情页 onStop
→ 详情页 onDestroy这套顺序里最容易被忽略的是:
onPause后通常 失去交互,但不一定立刻完全不可见onStop更接近 完全不可见onDestroy不是可靠收尾点,系统直接杀进程时你可能根本看不到它
对应 Lab:android-activity-lifecycle-basic · LifecycleLogActivity.kt · ActivityDetailActivity.kt · android-activity-lifecycle-compose · ComposeLifecycleActivity.kt · android-activity-lifecycle-java-xml · JavaLifecycleActivity.java
Activity 常见放置策略
| 回调 | 更适合做什么 | 不适合做什么 |
|---|---|---|
onCreate | inflate、依赖注入、一次性绑定、读取首次参数 | 每次回前台都要重复执行的刷新 |
onStart | 注册与“可见”相关的 listener、轻量 UI 准备 | 重 CPU / 重 IO 初始化 |
onResume | 恢复交互、相机预览、动画、焦点相关工作 | 无脑放全部网络刷新 |
onPause | 暂停动画、相机、输入、提交轻量状态 | 期望这里做稳定持久化大事务 |
onStop | 释放较重 UI 资源、停止可见性相关任务 | 指望它一定被调用后再杀进程 |
onDestroy | 最后解绑、日志观察 | 依赖它做唯一清理保障 |
3. Fragment 生命周期:宿主生命周期之外,还有 view 生命周期
Fragment 最大的坑不是回调多,而是 有两套生命周期:
- Fragment 实例生命周期
- Fragment View 生命周期
典型顺序:
text
Fragment 实例创建:
onAttach
→ onCreate
→ onCreateView
→ onViewCreated
→ onStart
→ onResume
销毁 View 但保留 Fragment 实例:
onPause
→ onStop
→ onDestroyView
最终销毁 Fragment:
onDestroy
→ onDetach这意味着:
Fragment字段可能还活着- 但
binding.root对应的 View 树已经被onDestroyView销毁 - 如果你在
onDestroyView之后还持有 ViewBinding / Adapter / listener,很容易泄漏整棵 View 树
Fragment 最重要的工程分层
| 放置位置 | 适合放什么 |
|---|---|
onCreate | 与 UI 无关、跟 Fragment 实例绑定的初始化 |
onViewCreated | binding、RecyclerView、点击事件、collect UI state |
onDestroyView | 置空 binding、解绑 view listener、清理 adapter 对 view 的引用 |
onDestroy | Fragment 级真正结束时的收尾 |
一句话记忆:
- Fragment 活着,不代表 View 还活着
viewLifecycleOwner才是 UI 观察、Flow collect、LiveData observe 更安全的边界
4. ViewModel:跨配置变更保内存态,不跨进程保命
ViewModel 最容易被高估。它解决的是:
- 旋转屏幕
- 多窗口尺寸变化
- 夜间模式切换导致宿主重建
此时宿主 Activity/Fragment 可能重建,但只要还是同一个 ViewModelStoreOwner,ViewModel 可复用。
它不解决的是:
- 进程被系统杀死
- 用户彻底划掉任务后重新冷启动
- 设备重启后的恢复
kotlin
class DetailViewModel(
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val articleId: String = checkNotNull(savedStateHandle["articleId"])
var uiState: DetailUiState = DetailUiState.Loading
private set
fun refresh() {
// 重新拉取数据,结果进内存态
}
}上面这种写法的真实语义是:
articleId这种 恢复页面所需的关键入参,适合进SavedStateHandle- 网络结果、组合后的 UI state 这种 纯内存派生态,适合放
ViewModel - 真正要跨进程重建的数据,还是要回源到 repository / DB / cache
5. SavedStateHandle:不是小型数据库,而是“系统恢复线索”
SavedStateHandle 和 onSaveInstanceState 的定位都应该克制:
适合放:
- 当前 tab index
- 搜索关键词
- 列表滚动位置 key
- 当前详情页 id
- 表单临时输入
不适合放:
- 大对象图
- 完整网络响应
- Bitmap / 大集合
- 复杂缓存
因为它本质上走的还是 Bundle / saved state registry 这条线:
- 大小有限
- 类型有限
- 目标是帮助系统在宿主重建后恢复“我刚才在哪、正在填什么”
一句话定位:
- ViewModel 是内存工作区
- SavedStateHandle 是恢复锚点
- DB / DataStore 是长期事实源
6. 配置变更:最常见的“假故障现场”
典型配置变更包括:
- 横竖屏切换
- 屏幕尺寸变化(折叠屏、多窗口)
- 字体缩放变化
- 深色模式变化
- 语言 / locale 变化
默认行为通常是宿主重建:
text
旧 Activity:
onPause → onStop → onDestroy
新 Activity:
onCreate → onStart → onResume此时观察重点:
- 哪些日志重复打印了 —— 说明你把一次性初始化写到了会重复的地方
- 哪些状态没了 —— 说明状态只放在实例字段 / View 里
- 哪些状态还在 —— 说明它们放在
ViewModel、SavedStateHandle或持久化层
不要轻易用 android:configChanges 去“吞配置变更”,除非你非常清楚自己在接管什么。很多团队为了避免重建,把框架兜底能力直接绕开,后面会遇到资源未重载、布局未重算、主题未切换完整等问题。
7. 前后台切换:页面还在,进程不一定安全;进程还在,页面不一定重建
前后台切换至少要分三层:
- 页面级:当前 Activity 从
onResume走到onPause/onStop - 应用级:整个 app 没有前台可交互页面了
- 进程级:进程可能继续存活,也可能稍后被系统回收
常见顺序:
text
按 Home 退后台:
onPause
→ onStop
从最近任务回前台:
onRestart
→ onStart
→ onResume工程上要分清:
- 页面回来不一定重建:所以
onResume会重复 - 页面没重建不等于数据可靠:网络连接、token、播放器、定位可能都要校验
- 退后台后不能假设进程一直活着:被 LMK 杀掉后,再回来就是新的冷/温启动路径
8. 进程恢复:真正的难点是“系统帮你记住了入口,但不会帮你重建业务世界”
当 app 退到后台后,系统可能因为内存压力回收进程。用户从最近任务或 launcher 回来时,常见现象是:
- task 栈入口看起来还在
- 但进程已经是新的
Application、单例、ViewModel、内存缓存全是新的一套- 只有 manifest / intent / saved state / 持久化数据还能帮你拼回现场
这就是为什么进程恢复经常暴露下面这些问题:
- 单例里缓存了登录态、feature flag、AB 配置,进程恢复后忘了重新拉
- 页面只靠
ViewModel存参数,进程恢复后拿不到articleId - 列表滚动位置靠 Activity 字段记,回来直接丢失
- deep link / push click 入口没做好幂等,恢复时又跳错页
进程恢复时的正确心智模型
| 层级 | 进程恢复后还能不能指望它存在 | 典型策略 |
|---|---|---|
| Activity / Fragment 实例字段 | 不能 | 从 savedState 或 repository 重建 |
| ViewModel | 不能 | 从 SavedStateHandle + data source 重建 |
| SavedStateHandle / Bundle | 部分能 | 只放恢复锚点 |
| DB / DataStore / 文件 | 能 | 作为事实源 |
| 服务端数据 | 能,但需重新同步 | 做幂等与缓存校验 |
9. 冷启动 / 温启动 / 热启动:不要把它们都叫“启动优化”
这一组概念是面试和线上监控里最高频的混淆点之一。
冷启动(Cold Start)
定义:进程不存在,系统新建进程并拉起 Application 与首个 Activity。
常见路径:
text
zygote fork process
→ Application.onCreate
→ Activity.onCreate
→ onStart
→ onResume
→ first frame观察点:
Application初始化是否过重- 首屏依赖是否阻塞主线程
- Splash / 首帧时间是否过长
- 首屏必须数据与可延迟数据是否拆开
温启动(Warm Start)
定义:进程还在,但首个 Activity 不在前台,需要重建或重新带到前台。
典型情况:
- App 在后台,进程仍存活
- 用户从最近任务或 launcher 回来
- 可能走
onRestart → onStart → onResume - 也可能因某个 Activity 已销毁而重新
onCreate
观察点:
- 重复请求是不是发生在
onResume - 页面恢复速度是不是被不必要初始化拖慢
- 内存缓存是否正确复用
热启动(Hot Start)
定义:进程和目标 Activity 都还在内存里,只是从暂停/停止态快速回到前台。
典型路径:
text
onRestart
→ onStart
→ onResume观察点:
- 恢复焦点、动画、播放器、相机是否正确
- 埋点、轮询、订阅有没有重复注册
- 返回栈恢复是否符合预期
三者对照
| 维度 | 冷启动 | 温启动 | 热启动 |
|---|---|---|---|
| 进程是否存在 | 否 | 是 | 是 |
Application.onCreate | 会执行 | 通常不执行 | 通常不执行 |
| 首个 Activity | 新建 | 可能新建,也可能复用 | 通常复用 |
| 关注重点 | 首帧、初始化拆分 | 恢复成本、重复初始化 | 回前台恢复与重复副作用 |
10. 执行顺序、预期行为、观察点:排障时要盯什么
场景 A:A 打开 B,再返回 A
执行顺序:
text
A onPause
B onCreate → onStart → onResume
A onStop
返回:
B onPause
A onRestart → onStart → onResume
B onStop → onDestroy预期行为:
- A 的 ViewModel 若作用域还在,应继续复用
- A 的
onResume会再次触发 - 如果列表刷新写在
onResume,返回时会再次请求
观察点:
- 是否发生重复网络请求
- 是否发生重复埋点
- RecyclerView / Compose 列表位置是否保住
场景 B:横竖屏切换
执行顺序(默认重建):
text
旧实例 onPause → onStop → onDestroy
新实例 onCreate → onStart → onResume预期行为:
ViewModel保住内存态SavedStateHandle保住轻量恢复状态- 纯 Activity 字段丢失
观察点:
- 输入框、tab、滚动位置是否恢复
- 初始化日志是否打了两遍
- listener 是否重复注册
场景 C:按 Home 退后台,再快速切回
执行顺序:
text
onPause → onStop
...
onRestart → onStart → onResume预期行为:
- 页面实例通常还在
Application.onCreate不应再执行onResume会重复触发
观察点:
- 轮询 / 播放器 / 相机恢复逻辑是否幂等
- token / 会话过期校验是否遗漏
场景 D:退后台后进程被杀,再从最近任务恢复
执行顺序:
text
新进程启动
→ Application.onCreate
→ 恢复入口 Activity.onCreate
→ onStart
→ onResume预期行为:
- 单例、ViewModel、内存缓存都视为全新
- 仅靠持久化和 saved state 恢复用户上下文
观察点:
- 页面能否拿回关键业务 id
- 是否因缺失内存缓存导致空白页 / 崩溃
- deeplink / push intent 是否幂等处理
Android / Flutter / Web / Backend 对照
| 维度 | Android | Flutter | Web | Backend |
|---|---|---|---|---|
| 页面宿主实例 | Activity / Fragment | Route + StatefulWidget + 宿主 Activity | 页面 / 路由组件 | 请求上下文 / handler |
| 可见但不可交互 | onStart 到 onResume 之间、或失焦场景 | route 在栈上但 app/state 变化 | tab 可见但失焦 | 几乎无直接类比 |
| UI 树重建 | 配置变更重建 view / compose 树 | build / element 重建更频繁 | 组件 rerender | 无 UI 树 |
| 跨重建内存态 | ViewModel | 页面级 state / provider / restoration 机制 | store / cache | 进程内 cache |
| 短期恢复锚点 | savedInstanceState / SavedStateHandle | restoration / route args | history state / session | 请求参数 / session |
| 进程被杀后恢复 | 依赖持久化 + saved state + intent | Flutter 自身 state 也要靠宿主/持久化兜底 | 刷新后重建前端 state | 进程重启后从 DB / MQ 恢复 |
对 Flutter 工程师最重要的映射是:
- Android 的 配置变更重建,不等于 Flutter 的普通
build - Android 的 ViewModel,更像“宿主页重建时还能活着的内存层”
- Android 的 SavedStateHandle,对应的是“给系统恢复留线索”,不是业务缓存层
- Flutter 若跑在 Android 宿主里,宿主进程恢复失败时,Flutter 页面 state 一样会丢
常见场景
1. 列表页进入详情页再返回,结果列表又请求一遍
最典型原因:
- 首次加载、回前台恢复、结果返回刷新三件事都写进了
onResume
更稳妥的拆法:
- 首次初始化:
onCreate/ViewModel init - 结果返回刷新:
ActivityResult/ Navigation result - 回前台校验:
onResume里只做幂等轻量检查
对应 Lab:android-activity-lifecycle-basic · LifecycleLogActivity.kt
2. Fragment 切换后内存泄漏
常见根因:
binding没在onDestroyView置空- adapter 持有旧 view context
viewLifecycleOwner外 collect 了 UI flow
这一类问题最容易误判成“Fragment 都还在,binding 多活一会儿没关系”。但真正被泄漏的往往不是 Fragment 实例本身,而是已经销毁的旧 View 树及其 context。只要 View 生命周期和 Fragment 实例生命周期没分开,切页几次后就会把整棵旧树越积越多。
对应 Lab:android-activity-lifecycle-basic · LifecycleLogActivity.kt
- 预期现象:view 相关对象应跟随
viewLifecycleOwner收尾;onDestroyView之后不应再有旧 binding / adapter 驱动 UI。 - 观察重点:是否在
onDestroyView之后仍能命中旧 view 回调、旧 adapter 更新或旧 binding 访问;如果能,说明清理边界还挂在 Fragment 实例层而不是 View 层。
3. 横竖屏切换后页面状态全丢
说明状态层级放错:
- 放在 View / Activity 字段:必丢
- 放在
ViewModel:跨配置变更通常可保住 - 放在
SavedStateHandle:可保住轻量恢复关键值 - 放在 DB / DataStore:跨进程最稳
4. 退后台回来定位/播放器/轮询逻辑异常
常见问题:
- 回来没恢复
- 恢复了两次
- 页面没重建,但 listener 重注册
这里常见误区是把“页面实例还在”误当成“副作用状态天然正确”。实际上退后台再回来时,最容易出错的不是页面有没有重建,而是播放器、定位、轮询、listener 这些副作用是否按前后台语义幂等恢复。
对应 Lab:android-activity-lifecycle-basic · LifecycleLogActivity.kt
- 预期现象:资源应在退后台时暂停,在回前台时按幂等规则恢复;页面没重建也不该导致 listener 重复注册。
- 观察重点:把资源暂停与恢复前校验拆开看——暂停多落在
onPause/onStop,恢复前校验多落在onStart/onResume;长任务或订阅状态应放到ViewModel或 service 协调,而不是只绑页面 callback。
5. 系统杀进程后,从最近任务恢复直接崩溃
高频根因:
- 页面拿参数只读内存单例
SavedStateHandle缺失关键 id- 入口 intent 解析与恢复路径没统一
- repository 初始化顺序依赖旧进程缓存
这类崩溃的本质不是“恢复入口没走通”,而是页面重建时仍偷偷依赖上一进程留下的内存世界。系统帮你保留的只是入口与少量恢复线索,不会帮你把旧单例、旧 ViewModel、旧内存缓存一起复活。
对应 Lab:android-activity-lifecycle-basic · LifecycleLogActivity.kt
- 预期现象:进程恢复后的页面重建必须只依赖 intent / saved state / persistent storage / server 四类来源;内存单例缺席时也应能重新拼回上下文。
- 观察重点:凡是代码里带着“刚才内存里应该还有”的假设,都要在强杀进程后重点验证;尤其检查关键 id、恢复入口解析和 repository 初始化顺序是否仍然成立。
常见坑、事故后果与排障
| 坑 | 线上现象 | 排障思路 | 修法 |
|---|---|---|---|
把所有刷新都写在 onResume | 返回页面重复请求、重复埋点 | 看网络日志与生命周期日志是否同频 | 拆成首次初始化 / 结果返回刷新 / 回前台校验 |
| 把状态只放 Activity/Fragment 字段 | 配置变更或进程恢复后状态丢失 | 旋转屏幕、开发者选项开“Don’t keep activities”复现 | 分层到 ViewModel / SavedStateHandle / DB |
以为 ViewModel 能跨进程恢复 | 被杀进程后页面空白或崩溃 | 强杀进程后从 recent task 恢复验证 | 用 SavedStateHandle 存锚点,真实数据回源 |
Fragment 持有 ViewBinding 到 onDestroy | 泄漏旧 View 树、奇怪 UI 回调 | LeakCanary / heap dump / 日志对比 onDestroyView | 在 onDestroyView 置空 binding |
用 android:configChanges 粗暴吞重建 | 暗黑模式/语言切换异常、资源不刷新 | 检查资源切换是否完整生效 | 除特殊场景外遵循系统默认重建 |
假设 onDestroy 一定执行 | 关键状态没落盘,偶现恢复失败 | 模拟系统杀进程 | 关键状态前移保存,不依赖最终回调 |
| 启动类型不分冷/温/热 | 启动优化方向错误 | 结合进程是否重建、Application.onCreate 是否执行判断 | 分类型打点和优化 |
与相近概念对比
| 概念 | 它真正解决什么 | 不解决什么 |
|---|---|---|
| Activity 生命周期 | 宿主页可见性、交互性、实例创建销毁 | 跨进程恢复 |
| Fragment 生命周期 | 复用子页面、与宿主协同 | View 生命周期自动安全管理(仍需手工处理) |
| ViewModel | 跨配置变更保留内存态 | 进程被杀后的恢复 |
| SavedStateHandle | 保留轻量恢复锚点 | 大数据缓存、复杂业务态 |
onSaveInstanceState | 系统级短期状态恢复 | 长期持久化 |
| DataStore / DB | 跨进程、跨重启事实源 | 页面级瞬时 UI state 细粒度体验 |
| 冷启动 | 新进程首帧路径 | 回前台重复副作用问题 |
| 温启动 | 进程存活下的恢复 | 首次进程初始化成本 |
| 热启动 | Activity 快速回前台 | 进程被杀后的可靠恢复 |
对应实验
各章节内已嵌入跳转链接;此处汇总全部 Lab:
| Lab | 说明 | 源码 |
|---|---|---|
| android-activity-lifecycle-basic | android-activity-lifecycle-basic | LifecycleLogActivity.kt · ActivityDetailActivity.kt |
| android-activity-lifecycle-compose | android-activity-lifecycle-compose | ComposeLifecycleActivity.kt |
| android-activity-lifecycle-java-xml | android-activity-lifecycle-java-xml | JavaLifecycleActivity.java |
复习检查题
为什么说 Android 生命周期要同时看“实例、可见性、状态、进程”四层,而不能只背 Activity 回调顺序?
答:因为同一个“页面回来”的现象,可能对应的是 Activity 复用、Fragment 只重建 view、ViewModel 还在、或者整个进程已重启。只背回调名无法判断状态该放哪层,也无法解释为什么有的状态丢、有的状态还在。
ViewModel和SavedStateHandle的职责边界是什么?答:
ViewModel适合承载跨配置变更的内存态,例如 UI state、已组合的数据、短期缓存;SavedStateHandle适合存关键恢复锚点,例如 id、tab、搜索词、滚动位置 key。ViewModel不跨进程,SavedStateHandle也不是数据库。为什么
Fragment最常见的泄漏点发生在onDestroyView之后,而不是onDestroy?答:因为 Fragment 实例可能还活着,但它的 View 树已经销毁。如果还持有 binding、adapter、listener,就会把旧 View 树和 context 一起留下来。UI 相关清理应该跟随
viewLifecycleOwner和onDestroyView。冷启动、温启动、热启动最关键的区分标准是什么?
答:先看进程是否存在,再看目标 Activity 是否需要新建。冷启动是进程不存在;温启动是进程存在但页面需要恢复/可能重建;热启动是进程和目标 Activity 通常都还在,只是快速回到前台。
为什么不能把关键业务状态只放在 Activity 字段或者单例缓存里?
答:因为配置变更会导致宿主重建,进程恢复会让内存字段和单例全部丢失。页面恢复必须能从 intent、saved state、持久化存储或服务端重新拼回上下文。
用户按 Home 退后台后很快切回来,为什么页面可能没重建,但逻辑还是出 bug?
答:因为
onResume会再次触发,轮询、播放器、埋点、listener 很可能重复恢复;同时 token、网络连接、权限状态可能已经变化。页面实例是否重建和副作用是否幂等是两回事。
速记
onCreate解决“建宿主”,onResume解决“能交互”,onStop更接近“用户看不见了”- Fragment 最大坑:实例还活着,不代表 View 还活着
ViewModel抗配置变更,不抗进程死亡SavedStateHandle存恢复锚点,不存业务大对象- 前后台切换要问:页面回来了?进程还在?副作用是否幂等?
- 启动优化先分冷 / 温 / 热,不然方向会错