运行时原理 (Runtime Internals)
Compose 的魔法核心在于它如何存储和更新 UI 树。这与传统的 View 系统(对象树)完全不同。
1. 什么是 Gap Buffer?
Compose 的数据结构设计灵感来自于文本编辑器。
文本编辑器需要频繁插入和删除字符。如果使用数组,每次插入都需要移动后面所有元素,效率极低。 Gap Buffer 是一个包含“间隙(Gap)”的数组。当光标移动时,Gap 也随之移动。在 Gap 处插入元素是 O(1) 的。
Compose 使用类似的结构来存储 UI 树的信息,因为它假设 UI 的变化也是局部的。
2. 插槽表 (Slot Table)
Compose 并不维护一个由 View 对象组成的树。相反,它将所有信息展平存储在一个线性数组中,称为 Slot Table。
数组中存储了:
- Composable 函数的参数
remember的值- UI 节点的引用
- 控制流信息 (if/else 标记)
text
[Group Start, Key: 123]
[Data: "Hello"] // 参数
[Group Start, Key: 456]
[Node: TextNode] // UI 节点
[Group End]
[Group End]3. 为什么 remember 能记住值?
当 remember { mutableStateOf(0) } 执行时:
初次组合 (Initial Composition):
- Compose 在 Slot Table 当前位置写入计算结果。
- 移动 Gap 指针。
重组 (Recomposition):
- Compose 再次执行到这里。
- 它查看 Slot Table 当前位置是否有值。
- 如果有,直接读取(这就“记住”了)。
- 如果参数变了(key 变了),它会更新 Slot Table 中的值。
4. 组 (Groups)
Slot Table 中的基本单位是 Group。Group 可以是:
- Replaceable Group: 对应
if/else等控制流。如果结构变了,整个 Group 被替换。 - Movable Group: 对应
key()。如果 Group 只是换了位置,Compose 会移动数据而不是销毁重建。 - Restartable Group: 对应 Composable 函数。它是重组的最小边界。
5. Applier
Compose Runtime 是通用的,它只负责管理 Slot Table 和变更差异 (Diff)。
Applier 负责将这些差异应用到具体的平台上。
UiApplier: 操作 AndroidView/LayoutNode。VectorApplier: 操作矢量图节点。Mosaic: 操作控制台字符(是的,Compose 可以写命令行 UI)。Redwood: 操作原生 iOS/Web 节点(用于 CMP)。