7.组件生命周期管理
# 7.组件生命周期管理
📍 本篇位置:第 5 卷 · 交互与系统 · 第 7 篇(与 5.1「窗口」、5.2「视图加载」一脉相承——5.1 解决「容器从哪里来」,5.2 解决「视图怎么挂上去」,5.7 解决「容器在时间维度上怎么活、怎么死」) 🎯 核心矛盾:「组件想长寿」 vs 「系统资源有限要让它死」——开发者希望组件「一直在那里、状态永远保留、随取随用」;但 OS 必须在内存/电量/可见性等多重压力下「优雅地让组件睡眠、回收、复活」。两难解法只有一个:把组件的「时间维度」拆成有限状态机,每个状态切换给开发者一个钩子——这就是生命周期(Lifecycle) 🧭 设计灵魂:生命周期 = 「状态机 + 钩子 + 所有权 + 可见性」四件套。任何组件框架(Android/iOS/React/Vue/Flutter/Compose/SwiftUI)都在这四件套上做选择——理解了通用骨架,所有平台的生命周期都是同一棵树的不同枝叶 🌐 跨平台覆盖:Android (Activity / Fragment / View / Service / ViewModel / Compose) · iOS (UIViewController / UIView / SwiftUI View / App lifecycle) · 前端 (React Class / Hooks / Vue Options / Vue Composition / Svelte / Angular) · 跨端 (Flutter StatefulWidget / RN / Compose Multiplatform) · 后端 (Spring Bean / @PostConstruct / @PreDestroy) · 桌面 (WPF / Qt / Electron) 🔗 延伸阅读:← 5.1 窗口核心设计思想 (opens new window) · ← 5.2 视图加载渲染设计 (opens new window) · ← 5.5 消息机制设计思想 (opens new window) · ← 5.6 跨进程通信设计 (opens new window) · → 5.8 页面导航与路由设计 (opens new window) · ↔ 3.x 并发之道 · ↔ 4.x 内存的真相 💡 通用心智:忘掉 onCreate/viewDidLoad/componentDidMount/initState 这些具体名词,记住一句话——生命周期 = 「组件在『出生 → 可见 → 交互 → 不可见 → 死亡』五段时间轴上的有限状态机,每段切换给一个钩子让你做资源管理」。Android 13 个回调、iOS 5 个回调、React 一个 useEffect、Vue 8 个钩子、Compose 的 DisposableEffect——本质都是同一棵树。核心理解:① 生命周期回调不是给你"业务用"的,是给你"资源管理"用的(订阅/解绑、注册/反注册);② 任何"耗时操作"放在错误的回调里都是事故(onPause 做存盘 = 黑屏);③ 组件销毁不等于对象释放——这是 90% 内存泄漏的根因——把这三点想明白,所有生命周期问题就有答案了。
# 目录
- 0.生命周期事故
- 1.为何需要生命周期
- 2.生命周期四件套
- 3.Android 生命周期
- 4.iOS 生命周期
- 5.前端生命周期
- 6.函数式生命周期
- 7.横向对比矩阵
- 8.反模式与陷阱
- 9.现代演进
- 10.心智与哲学
# 0.生命周期事故
场景:某电商 App 商品详情页,QA 报告:「随机概率黑屏 + Crash,无规律复现」。线上 Crashlytics 显示
IllegalStateException: Fragment not attached to a context,崩溃量占总崩溃 23%。
# 0.1 案例代码
class ProductDetailFragment : Fragment() {
private lateinit var productId: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
productId = arguments?.getString("id") ?: ""
// ★ 网络请求在 onCreate 启动
ApiService.getProduct(productId) { product ->
// ★ 回调可能在任何时刻到来——5s 后、10s 后、用户已经关闭页面
updateUI(product) // ❌ 如果 Fragment 已经 detach,这里崩溃
// ★ 本地存储——埋雷点 2
saveToCache(product) // 主线程 IO,可能 ANR
}
// ★ 注册全局事件总线
EventBus.register(this) // ❌ 没在 onDestroy 反注册
}
override fun onPause() {
super.onPause()
// ★ 在 onPause 做存盘操作
SharedPreferences.edit().putString("last_id", productId).commit() // ❌ 阻塞主线程
}
private fun updateUI(product: Product) {
// ★ 直接用 context / activity,没做存活检查
val tv = view?.findViewById<TextView>(R.id.title)
tv?.text = product.title
Glide.with(requireContext()).load(product.image).into(imageView) // ❌ requireContext 抛异常
}
}
# 0.2 四个核心问题
症状链:
用户进入商品页 → onCreate 发起网络请求 → 用户立刻按返回 → onDestroyView/onDetach 触发 →
网络回调 5s 后才到 → updateUI() 调用 requireContext() →
★ Fragment 已不再附加到 Activity → 抛 IllegalStateException → Crash
四个埋雷:
1. 异步回调没做"组件存活检查"
- 回调里直接用 requireContext()/requireActivity()
- 没用 viewLifecycleOwner / lifecycleScope
2. onPause 做了同步 IO
- SharedPreferences.commit() 是阻塞调用
- 主线程做 IO → 用户感知到"按返回慢半拍" → 进程被压后台 → ANR
3. EventBus.register 但没 unregister
- Fragment 被销毁了,但 EventBus 还持有它的强引用
- 内存泄漏 + 后续事件触发死对象
4. 把"业务逻辑"塞进生命周期回调
- onCreate 不是用来发网络请求的(应该是 ViewModel + Repository)
- onPause 不是用来存盘的(应该是异步 + 应用层节流)
# 0.3 事故启示
本质:开发者把"生命周期回调"当成了"业务执行点",把"组件引用"当成了"永生对象"
正确认知:
✓ 生命周期回调是「资源管理点」——订阅时机/解绑时机/资源回收时机
✓ 异步任务必须与组件生命周期"绑定",组件死则任务死
✓ 任何"组件持有"都要考虑"持有方比被持有方活得长"的情况
✓ 主线程 ≠ 万能线程——任何"看起来慢"的操作都不能放主线程
✓ 组件销毁 ≠ 对象释放——GC 不释放,是因为有人还在持有它
修复后的代码(5 处关键变更):
1. ApiService.getProduct → viewModel.loadProduct + viewLifecycleOwner.lifecycleScope
2. updateUI 用 LiveData.observe(viewLifecycleOwner) 自动管理
3. onPause 的存盘 → DataStore async + onStop 触发
4. EventBus → SharedFlow 收集在 lifecycleScope 里(自动取消)
5. 所有 requireContext() → 检查 isAdded + viewLifecycleOwnerLiveData
# 0.4 认知地图
事故是「果」,生命周期设计是「因」。本篇将沿着这条线展开:
§0 这个事故 ── 看懂"生命周期事故"的 5 个埋雷点(你现在在这)
↓
§1 为什么需要生命周期 ── 理解 OS 为什么要发明这套机制(三大根因)
↓
§2 通用四件套契约 ── 看清"任何平台都遵循"的生命周期骨架(跨端原理)
↓
§3-§6 各端实现 ── Android / iOS / React / Vue / Flutter / Compose(具体差异)
↓
§7 横向对比矩阵 ── 不同平台生命周期的「翻译表」
↓
§8 反模式 ── §0 事故只是冰山一角,还有 5 个常见埋雷
↓
§9 现代演进 ── 响应式 + 协程怎么让生命周期"自动管理"
↓
§10 收口 ── 跨端心智 + 与全卷呼应
# 1.为何需要生命周期
# 1.1 纯函数 vs 长生命
想象一个"没有生命周期"的世界:
main() {
Window w = createWindow();
w.show();
loop(); // 死循环处理事件
w.destroy();
}
这种「一进一出」的模型只适合:
✓ 命令行工具(grep、ls 一跑就退出)
✓ 简单脚本
但现代 UI 的本质是:
✗ 多窗口/多页面(Activity 栈、UINavigationController)
✗ 后台/前台切换(用户随时按 Home 键)
✗ 系统压力下被回收(内存不足)
✗ 配置变更(横竖屏、字体大小)
✗ 进程死亡又重启(用户回到 App)
没有生命周期就没有"组件何时该做什么"的契约——
开发者只能"猜"组件什么时候活、什么时候死、什么时候该释放资源。
# 1.2 三大根因
根因 1:所有权(Ownership)
UI 组件不是"程序员创建的"——是"系统创建的"
── Activity 不是你 new 出来的,是系统 newInstance() 出来的
── UIViewController 在导航栈中由 UINavigationController 管理
── React 组件由 React Reconciler 决定何时挂载
既然不是你创建的,你就不知道"它什么时候死"
── 系统必须给你"通知"——这就是 onDestroy / viewDidUnload / componentWillUnmount
根因 2:资源管理(Resource)
每个组件都持有"系统资源":
── 内存(视图树、Bitmap)
── CPU(动画、定时器)
── 网络(连接、订阅)
── 传感器(GPS、加速度计)
── 文件句柄(数据库、文件流)
资源必须"获取 → 使用 → 释放"配对,否则就泄漏
── 没有钩子,开发者无法在"恰当时机"释放
根因 3:可见性同步(Visibility)
用户感知到的 UI 状态:可见 / 不可见 / 半透明
必须与组件状态对齐:
── 不可见时不应耗电(停止动画、停止位置更新)
── 可见时立刻恢复(继续动画、刷新数据)
可见性变化 = 用户行为 → 必须给开发者切入点
# 1.3 设计目标
任何生命周期机制都在追求 5 个目标:
1. 可预测性(Predictability)
── 同样的用户操作 → 同样的回调序列
── 例:Android onCreate → onStart → onResume 永远是这个顺序
2. 完备性(Completeness)
── 任何"组件可能进入的状态"都有回调
── 例:iOS 有 viewWillAppear / viewDidAppear / viewWillDisappear / viewDidDisappear,前后都覆盖
3. 可配对性(Pairing)
── 资源"申请"的钩子一定有"释放"的钩子配对
── 例:onCreate ↔ onDestroy, onStart ↔ onStop, onResume ↔ onPause
4. 可绑定性(Bindability)
── 异步任务能绑到组件生命周期上"自动取消"
── 例:lifecycleScope / viewLifecycleOwner / SwiftUI .onAppear
5. 可恢复性(Recoverability)
── 组件死了能恢复状态
── 例:onSaveInstanceState / state restoration / SwiftUI @StateObject
# 2.生命周期四件套
本节是全篇骨架。剥离所有平台差异,任何生命周期机制都遵循这 4 个核心要素——理解了这 4 件套,从 Android 切到 iOS/React/Vue/Flutter 时不用重新学,只需要查"在新平台里叫什么"。
# 2.1 状态机
组件本质是「时间维度上的有限状态机」——5 个核心状态:
┌─────────────┐
│ INITIALIZED │ (对象已创建,但还没"挂"到系统)
└──────┬──────┘
│ onCreate / init / constructor
▼
┌─────────────┐
│ CREATED │ (已挂到系统,但尚未显示)
└──────┬──────┘
│ onStart / viewWillAppear / mounted
▼
┌─────────────┐
│ STARTED │ (已可见,但尚未获得焦点)
└──────┬──────┘
│ onResume / viewDidAppear / activated
▼
┌─────────────┐
│ RESUMED │ (可见 + 可交互 = 最完整状态) ★
└──────┬──────┘
│ onPause / viewWillDisappear / deactivated
▼
... 反向回到 STARTED → CREATED → DESTROYED
┌─────────────┐
│ DESTROYED │
└─────────────┘
状态机的「跨端不变量」:
- 状态机是「单向序」——不能从 INITIALIZED 直接跳到 RESUMED,必须经过 CREATED、STARTED。
- 状态变化必有触发源——系统事件(按 Home)、用户行为(按返回)、配置变更(横竖屏)。
- 每个状态都有「资源消耗等级」——RESUMED 资源最重(动画、传感器全开),DESTROYED 资源为零。
# 2.2 钩子
钩子是「状态切换的回调函数」——给开发者一个"切入点"。
通用钩子模型:
- 「进入状态前」钩子:will / before(让你做准备)
- 「进入状态后」钩子:did / after(让你做收尾)
- 「离开状态前」钩子:will + 反向动作
- 「离开状态后」钩子:did + 反向动作
iOS 完整体现了「前后双钩子」:
viewWillAppear (即将可见) → viewDidAppear (已经可见)
viewWillDisappear (即将隐藏) → viewDidDisappear (已经隐藏)
Android 简化了「只有 did」:
onCreate / onStart / onResume / onPause / onStop / onDestroy
(没有 willStart / willPause——是设计权衡)
React Hooks 反过来——「不分前后,只看依赖」:
useEffect(() => { ... return cleanup; }, [deps])
(函数式范式:副作用与生命周期统一)
# 2.3 所有权
谁创建组件 → 谁管生命周期 → 谁负责销毁。
七端的「所有权链」:
Android:
Application → ActivityManagerService → Activity → Fragment → View
(AMS 管 Activity 生死,Activity 管 Fragment,Fragment 管 View)
iOS:
UIApplication → UIWindow → RootVC → UINavigationController → ChildVC
(UIWindow 持有 RootVC,导航栈 push/pop 决定子 VC 生死)
React:
ReactDOM.render → Root Fiber → Tree of Fibers
(Reconciler 决定 mount/update/unmount)
Vue:
createApp().mount() → Root Component → Child Components
(响应式系统驱动 mount/unmount)
Flutter:
runApp → Element Tree → State Tree
(Element 是状态的所有者,Element 死则 State 死)
Compose:
setContent → Composition → @Composable Functions
(Composition 持有 RememberObservers)
SwiftUI:
@main App → Scene → View
(SwiftUI Runtime 管理 view 的 init/deinit)
「所有权链」决定了「死亡传播」:
父组件死 → 子组件全死 → 资源全释放
但「持有」会破坏这条链:
Fragment 持有 Activity 引用 → Activity 想死却死不掉(§8.3 反模式)
# 2.4 可见性
可见性是「用户视角」,组件状态是「系统视角」,两者必须同步。
用户可见性的「四态」:
1. 前台可见可交互:用户在看 + 在点 → 必须满血运行
2. 前台可见不可交互:弹了 Dialog 在上面 → 暂停动画但保留状态
3. 后台可见:分屏模式 / 画中画 → 减少 CPU 但保持渲染
4. 完全不可见:被遮挡 / 切后台 → 释放重资源(相机、GPS)
系统通过「可见性钩子」把这四态翻译为生命周期:
Android:
onResume (1) ↔ onPause (2) ↔ onStop (4)
Android Q+ 多窗口:(3) 也是 onResume,靠 isInPictureInPictureMode 区分
iOS:
viewDidAppear (1) ↔ viewWillDisappear (2) ↔ applicationDidEnterBackground (4)
Web:
document.visibilitychange / Page Visibility API
IntersectionObserver(元素级可见性)
「可见性 ≠ 存活性」是关键认知:
✓ 切后台 ≠ 销毁,App 仍在内存中
✓ 不可见时仍可执行(后台音乐、下载、定时器)
✗ 但不可见时应"克制"——苹果会因后台耗电杀 App
# 2.5 跨端名词矩阵
任何生命周期事件在七大端的「同名异姓」字典——下次切端时先查这张表:
| 通用概念 | Android Activity | iOS UIViewController | React Class | React Hooks | Vue Composition | Flutter | Compose | SwiftUI |
|---|---|---|---|---|---|---|---|---|
| 创建 | onCreate | init / loadView | constructor | useState 首次 | setup | initState | remember { } 首次 | init |
| 首次挂载 | onStart + onResume | viewDidLoad | componentDidMount | useEffect(()=>{}, []) | onMounted | didChangeDependencies | LaunchedEffect(Unit) | .onAppear |
| 即将可见 | (无显式回调) | viewWillAppear | (无) | (无) | onBeforeMount | (无) | (无) | .onAppear |
| 已可见 | onResume | viewDidAppear | (需自实现) | (需 IntersectionObserver) | (需自实现) | (需自实现) | (需自实现) | .onAppear |
| 更新前 | (隐式) | (隐式) | componentWillUpdate (已废弃) | (依赖变化时 useEffect) | onBeforeUpdate | didUpdateWidget | LaunchedEffect(key) | .onChange(of:) |
| 更新后 | (隐式) | (隐式) | componentDidUpdate | useEffect(()=>{}, [dep]) | onUpdated | didUpdateWidget | LaunchedEffect(key) | .onChange(of:) |
| 即将不可见 | onPause | viewWillDisappear | (需自实现) | (需 IntersectionObserver) | (需自实现) | deactivate | (需自实现) | .onDisappear |
| 已不可见 | onStop | viewDidDisappear | (需自实现) | (需自实现) | (需自实现) | (需自实现) | (需自实现) | .onDisappear |
| 销毁前 | onDestroy | deinit | componentWillUnmount | useEffect cleanup | onBeforeUnmount | dispose | DisposableEffect.onDispose | .onDisappear + deinit |
| 状态保存 | onSaveInstanceState | encodeRestorableState | (需自实现) | (需自实现) | (需自实现) | Storage | rememberSaveable | @SceneStorage |
| 状态恢复 | onRestoreInstanceState | decodeRestorableState | (需自实现) | (需自实现) | (需自实现) | (需自实现) | rememberSaveable | @SceneStorage |
| 配置变更 | onConfigurationChanged | traitCollectionDidChange | (需自实现) | (需 useEffect 监听) | (需 watch) | didChangeMetrics | (状态自动更新) | (状态自动更新) |
| 进入后台 | onStop + ProcessLifecycleOwner | applicationDidEnterBackground | visibilitychange | visibilitychange | visibilitychange | AppLifecycleState.paused | ScenePhase.background | ScenePhase.background |
| 资源释放钩子 | onDestroy | deinit (ARC) | componentWillUnmount | useEffect cleanup | onBeforeUnmount | dispose | DisposableEffect.onDispose | .onDisappear |
| 作用域绑定 | lifecycleScope / viewLifecycleOwner | Combine cancellables | AbortController | useEffect cleanup | effectScope | mounted flag | rememberCoroutineScope | .task modifier |
「四件套契约」的跨端不变量:
- 状态机的「五状态」是普世的——任何平台都能映射到
INIT → CREATED → STARTED → RESUMED → DESTROYED。 - 钩子的「数量」反映了平台的"心智成本"——iOS 5 个、Android 13 个、React Hooks 1 个,越少越简单但灵活性越低。
- 所有权链 = 树形结构——所有平台的组件都是树,父死则子全死,"打破链"就是反模式。
- 可见性钩子总比生命周期钩子细——可见性变化频繁(每次按 Home),生命周期变化稀疏(每次新建/销毁)。
- 现代框架都在向「函数式生命周期」收敛——React Hooks/Compose/SwiftUI 把"出生/死亡/更新"全部归约到「副作用」一个概念上。
给所有应用开发者的总记忆:
不论你在做 Android、iOS、Web、Flutter、Compose、SwiftUI——任何组件生命周期都是「四件套:状态机 / 钩子 / 所有权 / 可见性」的不同组合。下次遇到「为什么我的代码崩溃 / 内存泄漏 / 不刷新」时你能精准说出"问题在哪一件套":是状态机理解错(在错误状态做了错误事,§0 案例)/ 钩子用错(onPause 做重活,§8.1)/ 所有权弄丢(持有反向引用,§8.3)/ 可见性没监听(后台还在耗电),而不是"框架的 bug"。
# 3.Android 生命周期
# 3.1 Activity 七大回调
完整的 Activity 状态机(含异常情况):
[不存在]
│
│ startActivity()
▼
onCreate() ← 创建视图、初始化数据、绑定 ViewModel
│
▼
onStart() ← 可见但不可交互(如部分遮挡)
│
▼
onResume() ← 完全可见可交互 ★(最完整状态)
│
│ ←─────────── 用户按 Home / 弹窗
▼
onPause() ← 失去焦点(弹 Dialog、跳转、按 Home)
│
│ ←─────────── 应在 100ms 内返回(否则下个 Activity 等待)
▼
onStop() ← 完全不可见
│
▼
onDestroy() ← 销毁
│
▼
[被回收]
异常路径:
onPause → onResume (短暂失焦后恢复,如弹完 Dialog)
onStop → onRestart → onStart → onResume(从最近任务恢复)
七大回调的「资源管理职责」:
| 回调 | 时机 | 该做什么 | 不该做什么 |
|---|---|---|---|
onCreate | 仅一次,组件诞生 | 初始化视图、绑定 ViewModel、注册系统监听 | 不要发网络请求(应该让 ViewModel 做) |
onStart | 可见前 | 注册广播、订阅数据流 | 不要做耗时操作 |
onResume | 可交互时 | 启动动画、恢复 Camera/GPS、启动定时器 | 不要做 IO |
onPause | 失去焦点 | 暂停动画、传感器;异步保存草稿 | 严禁同步 IO/网络(100ms 限制) |
onStop | 完全不可见 | 释放重资源(Camera)、停止定时器 | 不要假设它一定会被调用(系统可直接杀进程) |
onDestroy | 销毁前 | 解绑监听、释放对象引用 | 不要依赖它做关键存盘(可能不被调用) |
onRestart | 从 Stop 恢复 | 刷新 UI(数据可能变化) | 不要重新初始化(保留原状态) |
关键认知 1:onPause 的 100ms 限制
为什么 onPause 严禁同步 IO?
startActivity B:
A.onPause() → [必须返回] → B.onCreate() → B.onStart() → B.onResume()
如果 A.onPause() 阻塞 5 秒:
→ B 的所有回调全部延迟 5 秒
→ 用户感知到「点击后 5 秒才看到新页面」
→ 误以为是 B 卡,其实是 A 在拖后腿
关键认知 2:onDestroy 不一定被调用
被回收的三种情况:
1. 正常销毁:finish() / 用户按返回 → onDestroy 被调用 ✓
2. 系统内存压力杀 Activity:onStop 后直接杀进程 → onDestroy 不调用 ✗
3. 用户在最近任务列表划掉:直接杀进程 → onDestroy 不调用 ✗
正确做法:
✓ 关键存盘放 onPause(异步)
✓ 资源解绑可以放 onDestroy(如果没调用,进程都死了无所谓)
✗ 不要把"必须执行"的事放 onDestroy
# 3.2 Fragment 13 回调
Fragment 比 Activity 多 6 个回调,本质是「视图与 Fragment 解耦」:
onAttach(context) ← Fragment 关联到 Activity
│
▼
onCreate(savedState) ← Fragment 实例创建(不含视图)
│
▼
onCreateView() ← ★ 创建视图(独立于 onCreate)
│
▼
onViewCreated() ← 视图创建完成,可绑定数据
│
▼
onStart() → onResume() ← 与 Activity 同步
│
▼
onPause() → onStop() ← 与 Activity 同步
│
▼
onDestroyView() ← ★ 销毁视图(但 Fragment 还在!)
│
▼
onDestroy() ← Fragment 实例销毁
│
▼
onDetach() ← 从 Activity 解关联
「Fragment 和 View 不同寿」的设计哲学:
为什么 Fragment 要区分 onCreate 和 onCreateView?
场景:ViewPager 中的 Fragment
- 用户滑动:当前 Fragment 视图销毁(onDestroyView),但 Fragment 实例保留
- 滑回来:重建视图(onCreateView),Fragment 实例不变
- 好处:保留状态 + 节省内存
这就引出 Fragment 开发的「黄金法则」:
★ 与 Fragment 共寿 → 用 lifecycleScope / lifecycle
★ 与 View 共寿 → 用 viewLifecycleOwner / viewLifecycleOwner.lifecycleScope
错误:
binding 在 onCreateView 创建 → 在 onDestroy 释放
bug:onDestroyView 后 binding 持有的视图已死,但还没置 null → 内存泄漏
正确:
binding 在 onCreateView 创建 → 在 onDestroyView 置 null
# 3.3 配置变更与恢复
Android 生命周期最难的部分——「组件死了但状态要活」。
三种「销毁」的本质区别:
① 配置变更(横竖屏、字体大小、语言)
默认行为:Activity 完全重建(onDestroy → onCreate)
恢复方式:onSaveInstanceState(Bundle) 保存 → onCreate(Bundle) 恢复
内存中:原 Activity 销毁,新 Activity 创建
② 系统内存压力(用户切到别的 App,内存不足)
行为:进程被杀,但「任务栈」保留
恢复方式:用户回到 App → 系统重建 Activity 栈,调 onCreate 传入 Bundle
关键:onSaveInstanceState 在 onPause/onStop 后调用,保 Bundle
③ 用户手动退出(划掉最近任务)
行为:进程被杀 + 任务栈清空
恢复方式:不恢复,重新启动 App
关键:所有未持久化数据都丢
保存机制对比:
onSaveInstanceState(Bundle) 适合:
✓ 临时 UI 状态(输入框文字、滚动位置、Tab 选中)
✓ 小数据量(< 1MB,Bundle 通过 Binder 传输)
✗ 不适合"长期数据"(应该入库)
ViewModel 适合:
✓ 跨配置变更保留数据(横竖屏不重建)
✗ 不跨进程死亡保留(进程死则 ViewModel 死)
SavedStateHandle (ViewModel 1.2+):
✓ ViewModel + onSaveInstanceState 的"集大成"
✓ 跨配置变更 + 跨进程死亡都能恢复
进程死亡恢复的「黄金组合」:
class DetailViewModel(
private val savedStateHandle: SavedStateHandle // 自动持久化
) : ViewModel() {
// 用户的输入草稿——进程死了也要恢复
val draft: MutableLiveData<String> = savedStateHandle.getLiveData("draft")
// 加载的商品详情——重启时重新加载即可
val product: LiveData<Product> = liveData {
emit(repo.loadProduct(savedStateHandle["productId"] ?: ""))
}
}
# 3.4 ViewModel 解耦
ViewModel 解决了 Android 生命周期的「世纪难题」:
问题:如何让数据"超越"Activity 的生命周期?
传统方案的痛点:
- static 字段 → 内存泄漏,多 Activity 共享乱套
- onSaveInstanceState → 只能存简单类型
- SharedPreferences → IO 慢,不适合复杂对象
- 单例 → 跨 Activity 生命周期混乱
ViewModel 的设计:
- 范围(Scope)= ViewModelStoreOwner(Activity / Fragment / NavGraph)
- 生命周期 = Scope 完全销毁时(不是配置变更)
- 内存中由 ViewModelStore 持有,配置变更时 Activity 重建但 ViewModel 保留
ViewModelStore 的"魔法":
① Activity#onRetainNonConfigurationInstance() ← 配置变更前调用
② 系统保存 ViewModelStore 到一个特殊位置
③ Activity 重建后从同一位置恢复
④ 新 Activity 通过 ViewModelProvider 拿到同一个 ViewModel 实例
# 4.iOS 生命周期
# 4.1 UIViewController 回调
iOS UIViewController 的生命周期比 Android 简洁得多——
原因是 iOS 用「ARC + 引用计数」管理对象,不需要 onStop/onRestart 那种复杂状态。
[init]
│
▼
loadView ← 加载 view 属性(一般不用重写)
│
▼
viewDidLoad ← ★ 视图层级加载完成(仅一次,等价 onCreate)
│
▼
viewWillAppear ← 即将显示(每次都调,含 push/pop 回来)
│
▼
viewIsAppearing ← (iOS 13+) 已开始显示但未结束动画
│
▼
viewDidAppear ← 已完全显示 ★
│
│ ←─────── 用户操作
▼
viewWillDisappear ← 即将隐藏
│
▼
viewDidDisappear ← 已隐藏
│
▼
deinit ← ARC 引用计数归零时调用(无显式钩子)
iOS 与 Android 的「设计哲学差异」:
| 维度 | Android | iOS |
|---|---|---|
| 回调数量 | 13 个(Fragment) | 5 个(UIViewController) |
| 资源释放 | onDestroy(不一定被调) | deinit(ARC 自动) |
| 状态保存 | onSaveInstanceState(显式) | encodeRestorableState(默认开启) |
| 配置变更 | 默认重建(要存状态) | 不重建,传 trait 变更 |
| 导航栈 | Activity 栈(系统管) | UINavigationController(开发者管) |
| 设计哲学 | 「系统主导」(被回收是常态) | 「开发者主导」(ARC 自动管理) |
# 4.2 ARC 与 deinit
iOS 的"隐式生命周期"——ARC 决定何时释放对象。
init → 引用计数+1
被强引用 → 引用计数+1
引用释放 → 引用计数-1
引用计数 = 0 → deinit 被调用
经典陷阱:循环引用(Retain Cycle)
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
// ❌ 循环引用:self → timer → closure → self
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.update()
}
}
}
// 结果:deinit 永远不调用 → 内存泄漏
正确做法:
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.update() // ✓ weak 打破循环
}
iOS 资源释放的「最佳实践」:
class CameraViewController: UIViewController {
var captureSession: AVCaptureSession?
var cancellables = Set<AnyCancellable>() // Combine 订阅集合
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startCamera()
subscribeToData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopCamera() // ✓ 不可见前停相机
}
deinit {
cancellables.removeAll() // 显式取消(其实 ARC 会自动做)
print("VC dealloc") // 用来验证没循环引用
}
}
# 4.3 SwiftUI 声明式
SwiftUI 完全颠覆了"命令式生命周期"——
View 不再是"对象",而是"描述"(Description)。
struct ContentView: View {
@State var count = 0
var body: some View {
VStack {
Text("\(count)")
Button("Tap") { count += 1 }
}
.onAppear { print("appear") } // ★ 等价 viewDidAppear
.onDisappear { print("disappear") } // ★ 等价 viewDidDisappear
.task { await loadData() } // ★ 绑定生命周期的异步任务
.onChange(of: count) { newValue in // ★ 状态变化钩子
print("changed: \(newValue)")
}
}
}
SwiftUI 的"心智革命":
✗ 不再问"组件什么时候创建"——SwiftUI 频繁创建/销毁 View 描述
✓ 只问"状态什么时候变化"——@State / @StateObject / @ObservedObject
@StateObject vs @ObservedObject:
@StateObject:当前 View 持有所有权,View 在则它在
@ObservedObject:父 View 传入,所有权在父
错误用法:
struct ParentView: View {
var body: some View {
ChildView()
}
}
struct ChildView: View {
@ObservedObject var vm = ViewModel() // ❌ 每次重渲染都新建!
}
正确用法:
struct ChildView: View {
@StateObject var vm = ViewModel() // ✓ View 持有所有权,只创建一次
}
# 5.前端生命周期
# 5.1 React Class 回调
React 16+ 的 Class 组件生命周期分三阶段:
挂载阶段(Mount):
constructor(props)
↓
getDerivedStateFromProps(props, state) // 静态方法
↓
render()
↓
componentDidMount() // ★ 等价 onResume / viewDidAppear
更新阶段(Update):
getDerivedStateFromProps
↓
shouldComponentUpdate(nextProps, nextState) // 性能优化点
↓
render()
↓
getSnapshotBeforeUpdate(prevProps, prevState) // DOM 变更前
↓
componentDidUpdate(prevProps, prevState, snapshot)
卸载阶段(Unmount):
componentWillUnmount() // ★ 等价 onDestroy / deinit
异常阶段:
static getDerivedStateFromError
componentDidCatch // 错误边界
React 已废弃的"Will" 系列:
componentWillMount // React 16.3 废弃
componentWillReceiveProps // React 16.3 废弃
componentWillUpdate // React 16.3 废弃
为什么废弃?
Fiber 架构允许"中断渲染"——这些 Will 钩子可能被调用多次
导致开发者在 willMount 发请求时被调用 N 次 → 资源浪费
React 团队的态度:
✓ 用 componentDidMount(保证只调一次)
✓ 用 getDerivedStateFromProps(纯函数,没副作用)
✓ 用 useEffect(Hooks 范式)
# 5.2 React useEffect
function MyComponent({ id }) {
const [data, setData] = useState(null);
// 等价 componentDidMount + componentWillUnmount
useEffect(() => {
const subscription = api.subscribe(id, setData);
return () => subscription.unsubscribe(); // ★ cleanup
}, []);
// 等价 componentDidUpdate(仅当 id 变化时)
useEffect(() => {
fetchData(id).then(setData);
}, [id]);
return <div>{data}</div>;
}
Hooks 的"心智模型":
- useEffect = 「副作用」 = 「与外部世界的交互」
- 依赖数组 = 「什么变化时重新跑」
- cleanup 函数 = 「上次副作用的回收」
经典陷阱:闭包捕获过期状态(Stale Closure)
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // ❌ count 永远是 0(闭包捕获)
}, 1000);
return () => clearInterval(timer);
}, []); // ❌ 依赖空 → 闭包捕获初始值
}
// 正确做法:
setCount(c => c + 1); // 函数式更新,不依赖外部 count
# 5.3 Vue Options 钩子
Vue 2/3 Options API 提供 8 个生命周期钩子:
beforeCreate ← data/methods 还没初始化
↓
created ← data/methods 已初始化(DOM 还没挂)
↓
beforeMount ← 即将挂载到 DOM
↓
mounted ← ★ DOM 已挂载(最常用)
↓
(响应式数据变化触发更新)
beforeUpdate ← 即将重新渲染
↓
updated ← 已重新渲染
↓
beforeUnmount ← 即将销毁(Vue 3,Vue 2 叫 beforeDestroy)
↓
unmounted ← 已销毁(Vue 2 叫 destroyed)
额外钩子:
activated ← keep-alive 缓存组件激活
deactivated ← keep-alive 缓存组件失活
errorCaptured ← 错误捕获
# 5.4 Vue Composition API
import { onMounted, onBeforeUnmount, ref, watch } from 'vue'
export default {
setup() {
const count = ref(0)
onMounted(() => {
console.log('mounted')
startTimer()
})
onBeforeUnmount(() => {
console.log('about to unmount')
stopTimer()
})
// 等价 useEffect with deps
watch(count, (newVal, oldVal) => {
console.log(`changed: ${oldVal} → ${newVal}`)
})
return { count }
}
}
Vue Composition vs React Hooks 的根本差异:
| 维度 | Vue Composition | React Hooks |
|---|---|---|
| setup 执行次数 | 仅 1 次 | 每次渲染都跑 |
| 状态依赖 | 响应式系统(Proxy)自动追踪 | 显式依赖数组 |
| 更新触发 | 响应式数据变化 | setState 调用 |
| 闭包陷阱 | 无 | 有(Stale Closure) |
| 逻辑复用 | composable 函数 | custom hooks |
| 心智 | 接近 OOP | 接近纯函数 |
# 6.函数式生命周期
# 6.1 Flutter Stateful 11 阶段
Flutter StatefulWidget 把"组件"拆成两个对象:
- Widget(不可变,频繁创建)
- State(可变,持久保存)
完整生命周期:
createState() ← 创建 State 对象(仅一次)
↓
initState() ← ★ State 初始化(等价 onCreate)
↓
didChangeDependencies ← 依赖变化(InheritedWidget 变了)
↓
build() ← 构建 Widget 树
↓
(父 Widget 重建时)
didUpdateWidget(old) ← Widget 配置更新
↓
build()
↓
(从树中移除)
deactivate() ← 暂时从树中移除(可能重新插入)
↓
dispose() ← ★ 最终销毁(等价 onDestroy)
Flutter 的"性能心智":
✗ Widget 频繁重建(每次 setState 整棵子树)
✓ State 持久保留(initState/dispose 只调一次)
→ 重对象(如 AnimationController)放 State,不要放 Widget
# 6.2 Compose remember
@Composable
fun MyScreen(id: String) {
// 等价 useState
var count by remember { mutableStateOf(0) }
// 等价 componentDidMount(仅首次执行)
LaunchedEffect(Unit) {
loadInitialData()
}
// 等价 useEffect with deps(id 变化时重新执行)
LaunchedEffect(id) {
loadProductDetails(id)
}
// 等价 useEffect with cleanup(最完整)
DisposableEffect(Unit) {
val listener = registerListener()
onDispose {
unregisterListener(listener) // ★ 离开 Composition 时执行
}
}
// 跨进程死亡保存状态
var draft by rememberSaveable { mutableStateOf("") }
}
Compose 副作用 API 全家桶:
| API | 作用 | 等价于 |
|---|---|---|
LaunchedEffect(key) | 协程作用域绑定 Composition | useEffect with deps |
DisposableEffect(key) | 需要清理的副作用 | useEffect + cleanup |
SideEffect | 每次成功重组都执行 | useEffect 无 deps |
rememberCoroutineScope | 在事件回调里启动协程 | useRef + AbortController |
rememberUpdatedState | 解决闭包过期问题 | useRef |
produceState | 把异步流转 State | 自定义 hook |
derivedStateOf | 派生状态(避免不必要重组) | useMemo |
snapshotFlow | State → Flow | (无对应) |
# 6.3 函数式 vs 命令式
两种范式的根本差异:
命令式(Android Activity / iOS UIViewController / Vue Options):
开发者写「在什么时机做什么」
onCreate { create view }
onResume { start sensor }
onPause { stop sensor }
onDestroy { release }
函数式(React Hooks / Compose / SwiftUI):
开发者写「什么状态对应什么副作用」
useEffect(() => {
const handle = startSensor()
return () => stopSensor(handle)
}, [isActive])
心智差异:
命令式:思考"时间线" → 在每个时间点做对的事
函数式:思考"状态依赖" → 描述状态如何决定副作用,框架决定何时执行
优势对比:
命令式 ✓ 直观,易理解("按顺序")
命令式 ✗ 容易遗漏配对(注册了忘解绑)
函数式 ✓ 配对自动(cleanup 强制写)
函数式 ✗ 心智弯曲,调试难(闭包陷阱、依赖数组)
# 7.横向对比矩阵
# 7.1 能力对齐表
| 维度 \ 平台 | Android | iOS | React Hooks | Vue Composition | Flutter | Compose | SwiftUI |
|---|---|---|---|---|---|---|---|
| 回调数量 | 13 (Fragment) | 5 | 1 (useEffect) | 7 | 11 | 6 (Effect 系列) | 4 (生命周期 modifier) |
| 范式 | 命令式 | 命令式 | 函数式 | 半函数式 | 命令式(State 内) | 函数式 | 函数式 |
| 创建钩子 | onCreate | viewDidLoad | useState 初始化 | setup | initState | remember 首次 | init |
| 销毁钩子 | onDestroy | deinit (ARC) | useEffect cleanup | onBeforeUnmount | dispose | DisposableEffect.onDispose | (ARC + onDisappear) |
| 可见性钩子 | onResume/onPause | viewDidAppear/Disappear | (需自实现) | (需自实现) | (需自实现) | (需自实现) | .onAppear/.onDisappear |
| 状态保存 | onSaveInstanceState + ViewModel | encodeRestorableState | (需自实现) | (需 store) | Storage 库 | rememberSaveable | @SceneStorage |
| 作用域绑定 | lifecycleScope | Combine cancellables | useEffect cleanup | effectScope | mounted flag | rememberCoroutineScope | .task modifier |
| 跨配置变更存活 | ViewModel | 自动(VC 不重建) | 自动(无重建) | 自动 | State 保留 | rememberSaveable | @StateObject |
| 心智成本 | 高(13 个回调) | 中(5 个) | 低(1 个)但有陷阱 | 中(7 个) | 中(11 个) | 低(5 个 Effect) | 极低(声明式) |
| 典型 bug | Fragment 持有 Activity | 闭包循环引用 | Stale closure | watch 死循环 | setState after dispose | rememberSaveable 不可序列化 | @ObservedObject 用错 |
# 7.2 设计哲学站位
命令式
│
Android ● │
(13 回调) │
│ ● iOS UIVC
│ (5 回调)
│
│● Flutter
│ (11 阶段)
─────────────┼───────────── 范式光谱
│
│● Vue Composition
│ (7 钩子)
│
Compose ● │● React Hooks
(6) │ (1 个 useEffect)
│
SwiftUI ●(4 modifier)
│
函数式
横轴:命令式 ←→ 函数式
纵轴:回调数量(多 ←→ 少)
「演化方向」:所有平台都在从"命令式 + 多回调"向"函数式 + 少 API"演化。
Android Activity → Compose
iOS UIKit → SwiftUI
Vue Options → Composition
React Class → Hooks
# 8.反模式与陷阱
§0 案例只是冰山一角,下面是 5 个最常见、最致命的反模式。这 5 个反模式覆盖了 90% 的生命周期事故。
# 8.1 onPause 耗时
症状:用户感知到「按返回慢半拍」、概率 ANR、Activity 切换卡顿。
错误代码:
override fun onPause() {
super.onPause()
// ❌ 同步写大文件
File(filesDir, "draft.txt").writeText(largeContent)
// ❌ 同步网络请求
api.uploadDraft(draft).execute()
// ❌ SharedPreferences.commit()
prefs.edit().putString("k", v).commit()
}
为什么是反模式:onPause 必须 100ms 内返回(系统硬性要求)——下一个 Activity 的所有生命周期都在等它。
正确做法:
override fun onPause() {
super.onPause()
// ✓ 异步存储
lifecycleScope.launch(Dispatchers.IO) {
File(filesDir, "draft.txt").writeText(largeContent)
}
// ✓ apply 替代 commit(异步)
prefs.edit().putString("k", v).apply()
// ✓ 仅停止"必须立即停"的(动画、传感器)
animation.pause()
locationManager.removeUpdates(this)
}
# 8.2 异步回调悬垂
症状:IllegalStateException: Fragment not attached、NullPointerException on view、内存泄漏。
错误代码:
override fun onCreateView(...) {
api.fetchData { data ->
// ❌ 5 秒后回调,Fragment 可能已 detach
textView.text = data.title // 崩溃:textView 已被回收
}
}
正确做法(三种方案):
// 方案 1:viewLifecycleOwner.lifecycleScope(推荐)
viewLifecycleOwner.lifecycleScope.launch {
val data = api.fetchData()
if (isAdded && view != null) { // 双保险
textView.text = data.title
}
}
// 方案 2:LiveData + observe(viewLifecycleOwner)
viewModel.data.observe(viewLifecycleOwner) { data ->
textView.text = data.title // Lifecycle 自动管理,组件死则不回调
}
// 方案 3:Flow.flowWithLifecycle
lifecycleScope.launch {
viewModel.dataFlow
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect { textView.text = it.title }
}
# 8.3 Fragment 强引用
症状:横屏后内存翻倍、LeakCanary 报警、配置变更后旧 Activity 不释放。
错误代码:
class MyFragment : Fragment() {
var activity: MainActivity? = null // ❌ 强引用 Activity
override fun onAttach(context: Context) {
super.onAttach(context)
activity = context as MainActivity // ❌ 持有 Activity 引用
}
// 配置变更:Activity 销毁,但 Fragment 还持有它 → 内存泄漏
}
正确做法:
// ✓ 用 requireActivity() / activity,而不是缓存
override fun doSomething() {
val act = activity ?: return // 用临时变量
act.someMethod()
}
// ✓ 跨 Fragment-Activity 通信用接口 + ViewModel
class MyFragment : Fragment() {
private val sharedVM: SharedViewModel by activityViewModels()
// 用 ViewModel 通信,而不是直接持有 Activity
}
# 8.4 闭包循环引用
症状(iOS):deinit 永远不调用、Instruments 显示内存持续增长。
错误代码(Swift):
class ViewController: UIViewController {
var dataLoader: DataLoader?
override func viewDidLoad() {
super.viewDidLoad()
// ❌ 循环引用:self → dataLoader → closure → self
dataLoader = DataLoader()
dataLoader?.onComplete = { data in
self.updateUI(data) // self 被强引用
}
}
}
正确做法:
// ✓ weak self 打破循环
dataLoader?.onComplete = { [weak self] data in
self?.updateUI(data)
}
// ✓ 如果一定不能 nil(如动画完成回调)
dataLoader?.onComplete = { [unowned self] data in
self.updateUI(data)
}
// ✓ Combine 自动管理
dataLoader.publisher
.sink { [weak self] data in self?.updateUI(data) }
.store(in: &cancellables) // VC 死则订阅自动取消
# 8.5 忘记反注册
症状:通知触发死对象、广播接收器在后台仍触发、EventBus 收到事件后崩溃。
错误代码:
// Android
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EventBus.register(this) // ❌ 没在 onDestroy 反注册
sensorManager.registerListener(this, sensor, RATE) // ❌
}
正确做法:
// 方案 1:配对回调(笨办法)
override fun onCreate(...) {
EventBus.register(this)
}
override fun onDestroy() {
EventBus.unregister(this)
super.onDestroy()
}
// 方案 2:Lifecycle-aware Observer(推荐)
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ✓ 自动绑定生命周期,无需手动反注册
lifecycle.addObserver(MyLocationObserver(this))
}
}
class MyLocationObserver(val context: Context) : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { startLocation() }
override fun onStop(owner: LifecycleOwner) { stopLocation() }
}
// 方案 3:协程作用域(Flow/StateFlow)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
eventBus.eventFlow.collect { handleEvent(it) }
// STARTED → STOPPED 时自动取消
}
}
五大反模式的「共同根因」:
所有反模式都源于「混淆了对象生命周期 和 业务生命周期」:
对象生命周期:内存中 new 到 GC/dealloc 的时间
业务生命周期:用户感知到"这页存在"的时间
正确的心智:
✗ 不要假设"对象还在 → 一切都正常"
✓ 要假设"对象可能在 → 但视图已死 / 上下文已变 / 数据已过期"
解决思路:
1. 任何"绑定关系"都必须有"自动解绑"机制
2. 任何"异步回调"都必须有"组件存活检查"
3. 任何"耗时操作"都必须从生命周期回调里搬走
# 9.现代演进
2018-2024 年,生命周期管理领域出现了**「响应式 + 协程 + 函数式」**三件套革命——把"手动管钩子"变成"框架自动管"。
# 9.1 LifecycleOwner 绑定
传统问题:手动在 onStart/onStop 中订阅/取消订阅,重复且易错。
现代方案(Jetpack):
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// ✓ LiveData 自动感知 Lifecycle
viewModel.user.observe(viewLifecycleOwner) { user ->
// 仅在 STARTED 及以上状态时调用
// 视图销毁则自动停止观察
updateUI(user)
}
// ✓ Flow + repeatOnLifecycle
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
render(state)
}
}
}
}
}
LifecycleOwner 的核心契约:
LifecycleOwner = "拥有生命周期的对象"
Activity / Fragment / ViewModel / 自定义类都可以实现
LiveData/Flow 的"魔法":
1. 订阅时传入 LifecycleOwner
2. 内部监听 owner.lifecycle.addObserver
3. ON_START → 开始投递数据
4. ON_STOP → 停止投递(数据缓存最新值)
5. ON_DESTROY → 自动反订阅
→ 开发者再也不用写 onStart/onStop 配对
# 9.2 协程作用域
class MyViewModel : ViewModel() {
init {
// viewModelScope:ViewModel 销毁时自动取消所有协程
viewModelScope.launch {
val data = repository.fetchData()
_state.value = data
}
}
}
class MyFragment : Fragment() {
override fun onViewCreated(...) {
// viewLifecycleOwner.lifecycleScope:View 销毁时自动取消
viewLifecycleOwner.lifecycleScope.launch {
val data = api.fetchData()
textView.text = data.title
}
}
}
三种 Scope 的「生命周期对齐」:
| Scope | 持有方 | 取消时机 | 用途 |
|---|---|---|---|
lifecycleScope (Activity/Fragment) | Activity/Fragment | onDestroy | 与组件共寿 |
viewLifecycleOwner.lifecycleScope | Fragment 的 View | onDestroyView | 与视图共寿(推荐) |
viewModelScope | ViewModel | onCleared | 跨配置变更存活 |
GlobalScope | 应用进程 | 进程死亡 | 危险(不推荐) |
# 9.3 Combine 自动取消
class ViewModel: ObservableObject {
@Published var data: Data?
private var cancellables = Set<AnyCancellable>()
func loadData() {
api.fetchData()
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in },
receiveValue: { [weak self] data in
self?.data = data
})
.store(in: &cancellables) // ★ 关键:与 ViewModel 共寿
}
// deinit 时 cancellables 自动取消所有订阅
}
// SwiftUI 的 .task modifier 更进一步:
struct ContentView: View {
var body: some View {
Text("Loading...")
.task {
// 自动绑定 View 生命周期
await loadData()
// View 消失则自动取消
}
}
}
# 9.4 副作用与生命周期
现代框架的"终极形态"——把生命周期完全隐藏在「副作用钩子」里:
React: useEffect(fn, deps)
Compose: LaunchedEffect(key) { fn() } / DisposableEffect(key) { onDispose { } }
SwiftUI: .task(id:) { } / .onChange(of:) { } / .onAppear { }
开发者不再思考:
✗ "组件什么时候创建"
✗ "组件什么时候销毁"
✗ "组件什么时候更新"
只思考:
✓ "依赖了什么状态"
✓ "状态变了要做什么"
✓ "做完要怎么清理"
→ 这是「程序员认知负担最低」的生命周期范式
→ 也是为什么 2024 年所有主流框架都在向这个方向收敛
演进总结:从命令式到响应式
第一代(1995-2010):传统命令式
─ MFC、UIKit、Android Activity
─ 钩子很多,开发者全靠手记
第二代(2008-2018):观察者模式
─ Android Lifecycle Library、RxJava、Combine
─ 用观察者模式自动管订阅生命周期
第三代(2018-2024):响应式 + 协程
─ Kotlin Coroutines、LiveData、StateFlow
─ 用作用域 + 取消机制自动管异步任务
第四代(2020+):函数式声明式
─ Compose、SwiftUI、React Hooks
─ 副作用 = 生命周期,框架完全自动管理
# 10.心智与哲学
# 10.1 跨端术语对照
任何生命周期开发者必备的「同名异姓」字典:
| 通用概念 | Android | iOS UIKit | SwiftUI | React | Vue | Flutter | Compose |
|---|---|---|---|---|---|---|---|
| 诞生 | onCreate | init / loadView | init | constructor | beforeCreate / created | createState / initState | remember 首次 |
| 挂载完成 | onStart | viewDidLoad | onAppear 首次 | componentDidMount | mounted | (initState 后) | LaunchedEffect(Unit) |
| 可交互 | onResume | viewDidAppear | onAppear | (隐式) | (隐式) | (build 后) | (隐式) |
| 失焦 | onPause | viewWillDisappear | onDisappear | (需自实现) | (需自实现) | deactivate | (需自实现) |
| 不可见 | onStop | viewDidDisappear | onDisappear | (需自实现) | (需自实现) | deactivate | (需自实现) |
| 销毁 | onDestroy | deinit | onDisappear + ARC | componentWillUnmount | unmounted | dispose | DisposableEffect.onDispose |
| 更新 | (隐式 setContentView) | (隐式) | onChange / body 重算 | componentDidUpdate | updated | didUpdateWidget | (重组) |
| 状态保存 | onSaveInstanceState | encodeRestorableState | @SceneStorage | (需自实现) | (需 store) | (需自实现) | rememberSaveable |
| 作用域绑定 | lifecycleScope | cancellables | .task | useEffect cleanup | effectScope | mounted flag | rememberCoroutineScope |
| 可见性监听 | LifecycleObserver | viewDidAppear/Disappear | onAppear/Disappear | IntersectionObserver | watch + visibility | VisibilityDetector | (需自实现) |
| 进入后台 | ProcessLifecycleOwner | applicationDidEnterBackground | ScenePhase.background | visibilitychange | visibilitychange | AppLifecycleState | ProcessLifecycleOwner |
| 配置变更 | onConfigurationChanged | traitCollectionDidChange | (自动) | (需手动) | (需手动) | didChangeMetrics | (自动) |
| 典型 bug | Fragment 持 Activity | 循环引用 | @ObservedObject 用错 | Stale closure | watch 死循环 | setState after dispose | rememberSaveable 不可序列化 |
| 性能埋雷 | onPause 做 IO | viewDidLoad 做网络 | body 做副作用 | useEffect 缺 deps | watch 触发 watch | initState 做重活 | SideEffect 滥用 |
把这张表贴在 IDE 旁边——切换平台时不再需要"重新学生命周期",只需要查"在新平台里它叫什么"。
# 10.2 本卷章节呼应
5.1 窗口核心设计思想 ─→ Activity/UIViewController 是"窗口的内容容器"
窗口生命周期 ⊃ 组件生命周期
addView/removeView 触发 onAttached/onDetached
5.2 视图加载渲染设计 ─→ onCreateView 是视图加载的入口
视图树挂载/卸载 = onViewCreated/onDestroyView
setContentView 触发整棵 View 树的生命周期
5.3 图形渲染管线原理 ─→ onResume 后才开始 Choreographer 渲染
onPause 后停止 invalidate
生命周期决定"什么时候 SurfaceFlinger 合成"
5.4 手势事件设计灵魂 ─→ 仅 RESUMED 状态才接收触摸事件
onPause 后 InputDispatcher 不再投递
生命周期是事件分发的"开关"
5.5 消息机制设计思想 ─→ Handler/runOnUiThread 必须在 STARTED+ 状态执行
lifecycleScope 是 Handler 的"现代封装"
生命周期 + 消息队列 = 异步任务的双保险
5.6 跨进程通信设计 ─→ Activity 生命周期回调通过 Binder 从 AMS 传递
onCreate/onResume/onDestroy 都跨进程而来
ServiceConnection 必须与组件生命周期对齐
5.8 页面导航与路由设计 ─→ 路由跳转 = startActivity = 触发完整生命周期序列
backstack 决定 Activity 何时进入 stopped
路由的"页面栈"就是生命周期的"时间轴"
5.9 响应式数据绑定设计 ─→ LiveData/StateFlow 必须绑 LifecycleOwner
"响应式" + "生命周期" 是孪生兄弟
数据流必须随组件死亡而停止
跨卷呼应:
- 第 3 卷·并发之道 ─→ lifecycleScope / viewModelScope 是协程生命周期管理
- 第 4 卷·内存的真相 ─→ 生命周期错乱 = 内存泄漏的 80% 根因
- 第 6 卷·包管理与构建 ─→ ViewModelProvider 用反射 + 工厂模式注入
- 第 7 卷·安全与加密 ─→ 敏感数据应在 onStop 时清空(被截屏防护)
# 10.3 通用心智口诀
1. 生命周期 = 「时间维度的有限状态机 + 钩子」 → 任何组件框架都遵循
2. 钩子是"资源管理点",不是"业务执行点" → 业务放 ViewModel,钩子做绑定/解绑
3. 配对原则:onCreate↔onDestroy、onResume↔onPause → 申请必有释放
4. 异步任务必须绑作用域 → lifecycleScope / .task / cancellables / useEffect cleanup
5. 组件销毁 ≠ 对象释放 → 内存泄漏的 90% 根因是有人还持有
6. 时间窗口陷阱:异步回调到来时组件可能已死 → 必须做存活检查
7. 现代演进:从"手管钩子"到"框架自动管" → 函数式 + 响应式是终极方向
最终升华:
生命周期是程序员与操作系统/框架之间的契约——OS 用「有限状态机 + 钩子」给程序员一个掌控时间维度的把柄;程序员用「资源管理 + 异步绑定」把这把柄变成稳定的应用。每一种生命周期机制都是某种"时间维度的封装":Android 用 13 个钩子追求完备、iOS 用 5 个钩子追求简洁、React Hooks 用 1 个钩子追求统一、SwiftUI 把生命周期完全隐藏在状态依赖里。
§0 事故的本质是把"对象生命周期"和"业务生命周期"搞混——异步回调来时对象还在、但组件已经死了。当你能"看见"每个钩子背后的状态转换、每个异步任务背后的作用域绑定时,你才真正驾驭了生命周期。
好的生命周期设计,让程序员"忘记"它存在(开发体验);好的程序员,永远"记得"组件可能随时死亡(防御性编程)。这个矛盾贯穿了从 1995 年 MFC 的 OnCreate 到 2024 年 SwiftUI 的 .task 的所有生命周期演进——它就是这个领域的灵魂。
# 延伸阅读
- 文档:Android Activity Lifecycle (opens new window)
- 文档:Apple UIViewController Lifecycle (opens new window)
- 文档:React useEffect (opens new window) · Vue Lifecycle (opens new window)
- 文档:Compose Side-effects (opens new window) · SwiftUI Task (opens new window)
- 论文:Out of the Tar Pit (Ben Moseley & Peter Marks, 2006) —— 关于"状态管理"的本质
- 源码:AndroidX Lifecycle、SwiftUI runtime、React Fiber、Vue Reactivity
- 工具:LeakCanary(Android 内存泄漏)、Instruments(iOS 内存)、React DevTools Profiler