将 Compose 的重组(Recomposition)流程说清楚,可以将其拆解为:触发、执行、优化、挂载四个阶段。
可以把它想象成:「数据变化」驱动「代码重跑」,最终「智能刷新」UI 的过程。
1. 触发阶段(监听与失效)
重组的起点永远是 State(状态)。
- 读取追踪:当第一次运行一个 Composable 函数时,Compose 框架会记录下:哪个函数读取了哪个 State 对象。
- 状态变化:当修改 mutableStateOf 的值时。
- 标记失效:Compose 立即将所有读取了该状态的 Composable 函数标记为 Invalid(失效)。它并不会立刻刷新,而是等待下一个垂直同步信号(Vsync)到来时统一处理。
2. 执行阶段(Composition / 代码重跑)
当系统准备好刷新时,它会重新运行那些被标记为"失效"的函数。
- 重新执行:函数带着最新的数据再次运行,生成一棵新的描述树(UI 蓝图)。
- 局部运行:Compose 非常聪明,如果函数 A 的状态没变,它只会重新运行函数 B。
- 位置记忆:在这个过程中,remember 起到了关键作用。它像是一个"存档点",让函数重新运行到这一行时,能拿回之前存的值,而不是重置。
3. 优化阶段(Skipping / 智能跳过)
这是 Compose 性能强大的核心:并不是所有被调用的函数都会重跑。
- 参数对比:在重新运行一个函数前,Compose 会对比它的输入参数。
- 判定跳过:如果参数是稳定的(比如 String、Int 或 data class),且值没有变化,Compose 会直接 Skip(跳过) 这个函数。
- 为什么需要 remember?:如果不给 List 加 remember,每次重组它都是一个"新地址",Compose 就会认为参数变了,无法触发跳过逻辑。
4. 挂载阶段(Applying Changes / 差异对比)
生成了新的"蓝图"后,Compose 不会把旧 UI 全部拆掉重建,而是做 Diff(差异对比)。
- 找出差异:对比旧树和新树。比如:发现只有左边 Text 的颜色变了,或者中间多了一个 Button。
- 最小化更新:只对发生变化的底层原生 UI(LayoutNode)进行修改。
- 绘制:最终触发 Android 系统的 Draw 流程,用户看到界面更新。
5. 重组的三个重要特性(开发者必知)
- 重组是乐观的:Compose 假设重组很快就能跑完。如果重组还没跑完,数据又变了,Compose 会直接掐断当前的重组,重新开始新的一轮。
- 重组是并发的:Compose 框架可以在后台线程处理重组逻辑(虽目前主线程较多),所以严禁在函数体里写 Side Effect(副作用),比如修改全局变量。
- 顺序不确定:不同的 Composable 函数可能以任意顺序运行,甚至并行运行。
总结
重组流程图:
State 改变 →\rightarrow→ 标记函数失效 →\rightarrow→ 重新运行函数(尝试跳过未变部分) →\rightarrow→ 生成新 UI 树 →\rightarrow→ Diff 差异 →\rightarrow→ 更新屏幕。
在开发 RankGroupScreen 或 AccountScreen 时频繁使用 remember,本质上就是在给 Compose 提供"跳过"的理由,从而通过优化"执行阶段"来让应用更流畅。
如果想看哪些函数被跳过了,哪些在重复跑,可以使用 Android Studio 的 Layout Inspector,它会用数字标出每个组件重组的次数。
想了解如何通过 Stable 注解 来进一步强制让 Compose 跳过某些不必要的重组吗?