Compose 渲染内核 (1):揭秘三阶段管线与 Recomposer 的律动

28 阅读2分钟

前言

作为 Compose 开发者,我们知道 UI 的更新分为三个阶段:Composition(组合)Layout(布局)Drawing(绘制)。但在这个宏观流程背后,源码层面是如何流转的?谁在调度这一切?

今天我们深入底层,理清每一帧像素诞生的全路径。


一、 渲染的源头:Recomposer

在 Compose 中,一切变化的起点通常是某个 State 的改变。但真正负责“感知变化并启动渲染”的幕后黑手是 Recomposer

  • VSync 绑定Recomposer 并不是实时运行的,它会通过平台特定的 MonotonicFrameClock 监听硬件的刷新信号(VSync)。
  • 并发调度:它运行在一个独立的协程中。当快照系统(Snapshot System)发现状态变动时,会通知 Recomposer:“活儿来了”。

二、 第一阶段:Composition(产生 UI 描述)

Recomposer 获得执行权,它会调用 Composition.recompose()

  1. 执行函数:Composable 函数开始执行,逻辑逻辑输出到 Slot Table(槽位表)
  2. 生成指令:这一阶段并不产生像素,而是产生一棵由 LayoutNode 组成的树。
  3. Applier 接口Applier 负责具体的树操作。比如在 Android 上,它是 UiApplier,负责维护 LayoutNode 树的增删改查。

三、 第二阶段:Layout(确定尺寸与位置)

这是性能优化的主战场,也是 O(n)O(n) 协议的执行地。

  1. 测量(Measure)LayoutNode 会调用其 MeasurePolicy。父节点将 Constraints 传给子节点。
  2. 放置(Place):子节点返回 Placeable,父节点决定其 offset
  3. 链式传递:这一步是严格的单向深度优先遍历。源码类 LayoutNodeWrapper(在最新版中演进为 NodeCoordinator)负责在 Modifier 链条间传递这些几何信息。

四、 第三阶段:Drawing(像素落地)

当位置确定后,进入真正的绘图环节。

  1. DrawScope:Compose 提供了一个平台无关的画布接口 DrawScope
  2. Canvas 注入:在渲染时,Compose 会将底层的原生 Canvas(在 Android 上是 android.graphics.Canvas,在 CMP 上是 SkiaCanvas)注入给 DrawScope
  3. 层级加速:如果使用了 graphicsLayer,Compose 会在底层创建一个独立的 RenderNode。这样当该层发生位移或透明度变化时,可以直接通过 GPU 变换,而不需要重新执行前两个阶段。

🏗️ 源码关键类清单 (建议阅读)

如果你想自己翻源码,请重点关注以下路径:

  • androidx.compose.runtime.Recomposer
  • androidx.compose.runtime.Composition
  • androidx.compose.ui.node.LayoutNode
  • androidx.compose.ui.layout.MeasurePolicy
  • androidx.compose.ui.platform.AndroidComposeView (连接 Android 系统的桥梁)

结语

理解了这三阶段管线,你就理解了为什么“推迟状态读取”能提升性能:如果我们在绘制阶段才读取状态,那么就能完美跳过组合和布局阶段。

下一篇,我们将探讨 Compose 跨端的核心心脏 —— Skiko 与 Skia 引擎的深度联动