Skip to content

Android 生命周期总览 ​

本篇覆盖声明:聚焦 Activity / Fragment / ViewModel / SavedStateHandle / 进程恢复 / 前后台切换 / 配置变更 / 冷启动、温启动、热启动。重点不是背回调名,而是建立 “宿主实例、UI 树、状态容器、进程存活、系统调度” 五条线一起看 的工程判断。

一句话定义 ​

Android 生命周期本质上是在回答两件事:系统现在允许你的页面做什么,以及 你的状态应该活到哪一层。

代码索引 ​

主题Lab 说明源码
Activity 基础生命周期顺序(Kotlin + XML)android-activity-lifecycle-basicLifecycleLogActivity.kt · ActivityDetailActivity.kt
Activity 生命周期 + Compose 组合树进入/退出android-activity-lifecycle-composeComposeLifecycleActivity.kt
Activity 基础生命周期顺序(Java + XML)android-activity-lifecycle-java-xmlJavaLifecycleActivity.java

推荐学习顺序 ​

  1. 先把 冷启动 / 温启动 / 热启动 三种入口与 onCreate/onStart/onResume 的关系吃透。
  2. 再看 Activity / Fragment 可见性与交互性,建立“谁先 onPause、谁后 onStop”的时序感。
  3. 再看 ViewModel / SavedStateHandle / 持久化 三层状态分工。
  4. 最后看 配置变更、前后台切换、进程恢复,把线上最常见的状态丢失、重复请求、重复埋点串起来。

为什么需要 ​

Android 生命周期问题几乎都不是“不会 API”,而是 状态放错层、工作挂错时机、错误假设进程一定活着:

  • 详情页返回后为什么列表页又请求了一次
    • 一句话答:常因误把数据加载写在 onResume() 或未区分首次进入与回到前台,导致页面每次获取焦点都重复触发网络调用。(详见下文 §4.1、§5及复习题 6)
  • 横竖屏切换后为什么输入框内容没了
    • 一句话答:配置变更导致 Activity 销毁重建,若状态仅存放在内存字段中且控件未设置 android:id 或未接入 SavedStateHandle,状态将随旧实例一同丢失。(详见下文 §1、§4.2及复习题 5)
  • 退后台回来为什么页面没重建,但轮询恢复了两次
    • 一句话答:退后台切回触发 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 工程师来说,真正重要的是:

  1. 知道什么状态该放哪层
  2. 知道什么回调会重复、什么回调不保证执行
  3. 知道系统回收、配置变更、宿主重建时哪些对象会活下来
  4. 能把 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 常见放置策略 ​

回调更适合做什么不适合做什么
onCreateinflate、依赖注入、一次性绑定、读取首次参数每次回前台都要重复执行的刷新
onStart注册与“可见”相关的 listener、轻量 UI 准备重 CPU / 重 IO 初始化
onResume恢复交互、相机预览、动画、焦点相关工作无脑放全部网络刷新
onPause暂停动画、相机、输入、提交轻量状态期望这里做稳定持久化大事务
onStop释放较重 UI 资源、停止可见性相关任务指望它一定被调用后再杀进程
onDestroy最后解绑、日志观察依赖它做唯一清理保障

3. Fragment 生命周期:宿主生命周期之外,还有 view 生命周期 ​

Fragment 最大的坑不是回调多,而是 有两套生命周期:

  1. Fragment 实例生命周期
  2. 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 实例绑定的初始化
onViewCreatedbinding、RecyclerView、点击事件、collect UI state
onDestroyView置空 binding、解绑 view listener、清理 adapter 对 view 的引用
onDestroyFragment 级真正结束时的收尾

一句话记忆:

  • 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

此时观察重点:

  1. 哪些日志重复打印了 —— 说明你把一次性初始化写到了会重复的地方
  2. 哪些状态没了 —— 说明状态只放在实例字段 / View 里
  3. 哪些状态还在 —— 说明它们放在 ViewModel、SavedStateHandle 或持久化层

不要轻易用 android:configChanges 去“吞配置变更”,除非你非常清楚自己在接管什么。很多团队为了避免重建,把框架兜底能力直接绕开,后面会遇到资源未重载、布局未重算、主题未切换完整等问题。

7. 前后台切换:页面还在,进程不一定安全;进程还在,页面不一定重建 ​

前后台切换至少要分三层:

  1. 页面级:当前 Activity 从 onResume 走到 onPause/onStop
  2. 应用级:整个 app 没有前台可交互页面了
  3. 进程级:进程可能继续存活,也可能稍后被系统回收

常见顺序:

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 对照 ​

维度AndroidFlutterWebBackend
页面宿主实例Activity / FragmentRoute + StatefulWidget + 宿主 Activity页面 / 路由组件请求上下文 / handler
可见但不可交互onStart 到 onResume 之间、或失焦场景route 在栈上但 app/state 变化tab 可见但失焦几乎无直接类比
UI 树重建配置变更重建 view / compose 树build / element 重建更频繁组件 rerender无 UI 树
跨重建内存态ViewModel页面级 state / provider / restoration 机制store / cache进程内 cache
短期恢复锚点savedInstanceState / SavedStateHandlerestoration / route argshistory state / session请求参数 / session
进程被杀后恢复依赖持久化 + saved state + intentFlutter 自身 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-basicandroid-activity-lifecycle-basicLifecycleLogActivity.kt · ActivityDetailActivity.kt
android-activity-lifecycle-composeandroid-activity-lifecycle-composeComposeLifecycleActivity.kt
android-activity-lifecycle-java-xmlandroid-activity-lifecycle-java-xmlJavaLifecycleActivity.java

复习检查题 ​

  1. 为什么说 Android 生命周期要同时看“实例、可见性、状态、进程”四层,而不能只背 Activity 回调顺序?

    答:因为同一个“页面回来”的现象,可能对应的是 Activity 复用、Fragment 只重建 view、ViewModel 还在、或者整个进程已重启。只背回调名无法判断状态该放哪层,也无法解释为什么有的状态丢、有的状态还在。

  2. ViewModel 和 SavedStateHandle 的职责边界是什么?

    答:ViewModel 适合承载跨配置变更的内存态,例如 UI state、已组合的数据、短期缓存;SavedStateHandle 适合存关键恢复锚点,例如 id、tab、搜索词、滚动位置 key。ViewModel 不跨进程,SavedStateHandle 也不是数据库。

  3. 为什么 Fragment 最常见的泄漏点发生在 onDestroyView 之后,而不是 onDestroy?

    答:因为 Fragment 实例可能还活着,但它的 View 树已经销毁。如果还持有 binding、adapter、listener,就会把旧 View 树和 context 一起留下来。UI 相关清理应该跟随 viewLifecycleOwner 和 onDestroyView。

  4. 冷启动、温启动、热启动最关键的区分标准是什么?

    答:先看进程是否存在,再看目标 Activity 是否需要新建。冷启动是进程不存在;温启动是进程存在但页面需要恢复/可能重建;热启动是进程和目标 Activity 通常都还在,只是快速回到前台。

  5. 为什么不能把关键业务状态只放在 Activity 字段或者单例缓存里?

    答:因为配置变更会导致宿主重建,进程恢复会让内存字段和单例全部丢失。页面恢复必须能从 intent、saved state、持久化存储或服务端重新拼回上下文。

  6. 用户按 Home 退后台后很快切回来,为什么页面可能没重建,但逻辑还是出 bug?

    答:因为 onResume 会再次触发,轮询、播放器、埋点、listener 很可能重复恢复;同时 token、网络连接、权限状态可能已经变化。页面实例是否重建和副作用是否幂等是两回事。

速记 ​

  • onCreate 解决“建宿主”,onResume 解决“能交互”,onStop 更接近“用户看不见了”
  • Fragment 最大坑:实例还活着,不代表 View 还活着
  • ViewModel 抗配置变更,不抗进程死亡
  • SavedStateHandle 存恢复锚点,不存业务大对象
  • 前后台切换要问:页面回来了?进程还在?副作用是否幂等?
  • 启动优化先分冷 / 温 / 热,不然方向会错

站点构建时间:2026/8/24 23:43:17