前言
作为 Compose 开发者,我们知道 UI 的更新分为三个阶段:Composition(组合)、Layout(布局) 和 Drawing(绘制)。但在这个宏观流程背后,源码层面是如何流转的?谁在调度这一切?
今天我们深入底层,理清每一帧像素诞生的全路径。
一、 渲染的源头:Recomposer
在 Compose 中,一切变化的起点通常是某个 State 的改变。但真正负责“感知变化并启动渲染”的幕后黑手是 Recomposer。
- VSync 绑定:
Recomposer并不是实时运行的,它会通过平台特定的MonotonicFrameClock监听硬件的刷新信号(VSync)。 - 并发调度:它运行在一个独立的协程中。当快照系统(Snapshot System)发现状态变动时,会通知
Recomposer:“活儿来了”。
二、 第一阶段:Composition(产生 UI 描述)
当 Recomposer 获得执行权,它会调用 Composition.recompose()。
- 执行函数:Composable 函数开始执行,逻辑逻辑输出到 Slot Table(槽位表)。
- 生成指令:这一阶段并不产生像素,而是产生一棵由
LayoutNode组成的树。 - Applier 接口:
Applier负责具体的树操作。比如在 Android 上,它是UiApplier,负责维护LayoutNode树的增删改查。
三、 第二阶段:Layout(确定尺寸与位置)
这是性能优化的主战场,也是 协议的执行地。
- 测量(Measure):
LayoutNode会调用其MeasurePolicy。父节点将Constraints传给子节点。 - 放置(Place):子节点返回
Placeable,父节点决定其offset。 - 链式传递:这一步是严格的单向深度优先遍历。源码类
LayoutNodeWrapper(在最新版中演进为NodeCoordinator)负责在 Modifier 链条间传递这些几何信息。
四、 第三阶段:Drawing(像素落地)
当位置确定后,进入真正的绘图环节。
- DrawScope:Compose 提供了一个平台无关的画布接口
DrawScope。 - Canvas 注入:在渲染时,Compose 会将底层的原生 Canvas(在 Android 上是
android.graphics.Canvas,在 CMP 上是SkiaCanvas)注入给DrawScope。 - 层级加速:如果使用了
graphicsLayer,Compose 会在底层创建一个独立的 RenderNode。这样当该层发生位移或透明度变化时,可以直接通过 GPU 变换,而不需要重新执行前两个阶段。
🏗️ 源码关键类清单 (建议阅读)
如果你想自己翻源码,请重点关注以下路径:
androidx.compose.runtime.Recomposerandroidx.compose.runtime.Compositionandroidx.compose.ui.node.LayoutNodeandroidx.compose.ui.layout.MeasurePolicyandroidx.compose.ui.platform.AndroidComposeView(连接 Android 系统的桥梁)
结语
理解了这三阶段管线,你就理解了为什么“推迟状态读取”能提升性能:如果我们在绘制阶段才读取状态,那么就能完美跳过组合和布局阶段。
下一篇,我们将探讨 Compose 跨端的核心心脏 —— Skiko 与 Skia 引擎的深度联动。