Compose Runtime 是 Jetpack Compose 的核心引擎,它负责将你编写的 @Composable 函数转化为实际的 UI。本文将深入探索 Runtime 的内部工作原理,包括 Composition、Composer、Applier 和 Node Tree 的协作机制。
I. Runtime 架构总览
Compose Runtime 可以分为三个核心层次:
1.1 核心组件职责
- Composition:管理整个组合过程,持有 Slot Table 和 Applier
- Composer:执行 @Composable 函数,协调读写 Slot Table
- Slot Table:存储组合状态、Groups 和 Slots 数据
- Applier:将组合变更转换为 Node Tree 操作
- Node Tree:最终的 UI 树结构(Android 上是 LayoutNode)
II. Composition 详解
2.1 什么是 Composition
Composition 是 Compose Runtime 的核心类,它代表一次「组合」。每个 ComposeView 或 setContent 都会创建一个 Composition 实例:
// Composition 的简化结构
class CompositionImpl(
private val parent: CompositionContext,
private val applier: Applier<*>,
) : Composition {
// Slot Table 存储组合状态
private val slotTable = SlotTable()
// 待处理的失效(需要重组的作用域)
private val invalidations = mutableListOf<RecomposeScope>()
// Composer 实例
private val composer: ComposerImpl
// 执行组合或重组
override fun setContent(content: @Composable () -> Unit) {
// 初始组合
doCompose(content)
}
internal fun recompose(): Boolean {
// 处理失效,执行重组
return doRecompose()
}
}
2.2 Composition 的生命周期
III. Composer 执行机制
3.1 Composer 的角色
Composer 是执行 @Composable 函数的「指挥官」。每个 @Composable 函数编译后都会接收一个隐式的 Composer 参数:
// 原始代码
@Composable
fun Greeting(name: String) {
Text("Hello, $name")
}
// 编译后(简化)
fun Greeting(
name: String,
$composer: Composer,
$changed: Int
) {
$composer.startRestartGroup(key = 123)
if ($changed != 0 || !$composer.skipping) {
Text("Hello, $name", $composer, 0)
}
$composer.endRestartGroup()?.updateScope {
Greeting(name, $composer, $changed or 1)
}
}
3.2 Composer 核心 API
interface Composer {
// 是否正在初始组合
val inserting: Boolean
// 是否可以跳过当前作用域
val skipping: Boolean
// 开始一个 Group
fun startRestartGroup(key: Int): Composer
fun endRestartGroup(): ScopeUpdateScope?
// 记住值
fun <T> cache(invalid: Boolean, block: () -> T): T
// 检查参数是否变更
fun changed(value: Any?): Boolean
// 发射节点
fun <T> createNode(factory: () -> T)
fun useNode()
// 记录 Side Effect
fun recordSideEffect(effect: () -> Unit)
}
3.3 Groups 与位置记忆
Composer 使用 Groups 来组织 Slot Table 中的数据。每个 @Composable 调用都会创建一个 Group:
@Composable
fun Counter() {
// Group 1: Counter 函数本身
var count by remember { mutableStateOf(0) } // Slot: count 状态
Column { // Group 2: Column
Text("Count: $count") // Group 3: Text
Button(onClick = { count++ }) { // Group 4: Button
Text("+") // Group 5: 嵌套 Text
}
}
}
IV. Applier 与 Node Tree
4.1 Applier 接口
Applier 是连接 Composition 和 Node Tree 的桥梁。它定义了如何操作节点树:
interface Applier<N> {
// 当前节点
val current: N
// 向下移动到子节点
fun down(node: N)
// 向上返回父节点
fun up()
// 插入节点
fun insertTopDown(index: Int, instance: N)
fun insertBottomUp(index: Int, instance: N)
// 移除节点
fun remove(index: Int, count: Int)
// 移动节点
fun move(from: Int, to: Int, count: Int)
// 清空所有子节点
fun clear()
}
4.2 UiApplier - Android 实现
在 Android Compose UI 中,UiApplier 负责操作 LayoutNode 树:
internal class UiApplier(
root: LayoutNode
) : AbstractApplier<LayoutNode>(root) {
override fun insertTopDown(index: Int, instance: LayoutNode) {
// 自顶向下插入,不做任何操作
}
override fun insertBottomUp(index: Int, instance: LayoutNode) {
// 自底向上插入,将节点添加到当前节点的子节点
current.insertAt(index, instance)
}
override fun remove(index: Int, count: Int) {
current.removeAt(index, count)
}
override fun move(from: Int, to: Int, count: Int) {
current.move(from, to, count)
}
override fun onClear() {
root.removeAll()
}
}
Compose UI 使用 insertBottomUp 策略,因为 LayoutNode 的测量/布局依赖于子节点。自底向上插入可以确保在父节点附加时,子节点已经准备就绪,减少不必要的重新测量。
4.3 Node 发射过程
当遇到 Layout 类型的 Composable(如 Box, Column, Text)时,Composer 会发射节点:
// Layout Composable 的简化实现
@Composable
inline fun Layout(
content: @Composable () -> Unit,
measurePolicy: MeasurePolicy
) {
val materialized = currentComposer.materialize(content)
ReusableComposeNode<LayoutNode, Applier<Any>>(
factory = { LayoutNode() },
update = {
set(measurePolicy) { this.measurePolicy = it }
},
content = materialized
)
}
// ReusableComposeNode 内部
@Composable
inline fun <T, E> ReusableComposeNode(
noinline factory: () -> T,
update: @DisallowComposableCalls Updater<T>.() -> Unit,
content: @Composable () -> Unit
) {
if (currentComposer.inserting) {
// 初始组合:创建新节点
currentComposer.createNode(factory)
} else {
// 重组:复用现有节点
currentComposer.useNode()
}
// 更新节点属性
Updater<T>(currentComposer).update()
// 递归处理子内容
content()
}
V. 组合执行流程
5.1 初始组合流程
// 1. 创建 Composition
val composition = Composition(
applier = UiApplier(rootLayoutNode),
parent = recomposer
)
// 2. 设置内容
composition.setContent {
MyApp()
}
// 内部执行过程:
private fun doCompose(content: @Composable () -> Unit) {
// 1. 开始写入 Slot Table
slotTable.write { writer ->
composer.composeContent(content)
}
// 2. 应用变更到 Node Tree
applier.applyChanges()
// 3. 执行 Side Effects
runSideEffects()
}
5.2 重组流程
internal fun recompose(): Boolean {
if (invalidations.isEmpty()) return false
// 收集需要重组的作用域
val toRecompose = invalidations.toList()
invalidations.clear()
// 对每个作用域执行重组
slotTable.write { writer ->
for (scope in toRecompose) {
// 定位到该作用域在 Slot Table 中的位置
composer.recompose(scope)
}
}
// 应用变更
val changes = composer.collectChanges()
applier.applyChanges(changes)
return true
}
VI. Remember 与 State 的存储
6.1 remember 的实现
@Composable
inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T {
return currentComposer.cache(
invalid = false, // 永远有效,不重新计算
calculation
)
}
@Composable
inline fun <T> remember(
key1: Any?,
calculation: @DisallowComposableCalls () -> T
): T {
return currentComposer.cache(
invalid = currentComposer.changed(key1), // key 变化时重新计算
calculation
)
}
// Composer.cache 实现
fun <T> cache(invalid: Boolean, block: () -> T): T {
return if (inserting || invalid) {
// 初始组合或失效时,计算并存储
val value = block()
updateCachedValue(value)
value
} else {
// 重组时,从 Slot Table 读取
readCachedValue()
}
}
6.2 State 与重组的关联
@Composable
fun Counter() {
// 1. remember 将 MutableState 存入 Slot Table
var count by remember { mutableStateOf(0) }
// 2. 读取 count.value 时,Snapshot 系统记录:
// "当前 RecomposeScope 依赖于这个 State"
Text("Count: $count") // 触发 count.value 读取
// 3. 点击时,count.value 被写入
// Snapshot 系统通知所有依赖的 RecomposeScope
Button(onClick = { count++ }) {
Text("+")
}
}
每个 @Composable 函数执行时会创建 RecomposeScope。当函数内部读取 State 时,Snapshot 系统会将这个 State 与 RecomposeScope 关联。State 变化时,所有关联的 Scope 都会被标记为失效。
VII. 自定义 Applier:超越 UI
7.1 Compose 的通用性
Compose Runtime 并不局限于 UI。通过自定义 Applier,你可以将 Compose 用于任何树形结构:
// 自定义节点类型
class StringNode(var value: String = "") {
val children = mutableListOf<StringNode>()
}
// 自定义 Applier
class StringApplier(root: StringNode) : AbstractApplier<StringNode>(root) {
override fun insertBottomUp(index: Int, instance: StringNode) {
current.children.add(index, instance)
}
override fun remove(index: Int, count: Int) {
current.children.subList(index, index + count).clear()
}
override fun move(from: Int, to: Int, count: Int) {
val dest = if (from > to) to else to - count
val moved = current.children.subList(from, from + count).toList()
current.children.subList(from, from + count).clear()
current.children.addAll(dest, moved)
}
override fun onClear() {
root.children.clear()
}
}
// 使用自定义 Composition
fun renderToString(content: @Composable () -> Unit): String {
val root = StringNode()
val composition = Composition(
applier = StringApplier(root),
parent = Recomposer(Dispatchers.Main)
)
composition.setContent(content)
return root.buildString()
}
7.2 真实案例
- Compose for Web:Applier 操作 DOM 节点
- Compose for Desktop:Applier 操作 Skia 渲染节点
- Molecule:将 Compose 用于业务逻辑状态管理
- Mosaic:将 Compose 用于终端 UI
VIII. 性能优化视角
8.1 跳过 (Skipping) 机制
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count") // 会重组
ExpensiveChild() // 可以跳过!
Button(onClick = { count++ }) {
Text("+")
}
}
}
@Composable
fun ExpensiveChild() {
// 没有读取任何变化的状态
// Composer.skipping = true,整个函数被跳过
Text("I'm expensive but stable")
}
8.2 智能重组范围
IX. 总结
- Composition 是 Compose Runtime 的核心,管理整个组合过程
- Composer 执行 @Composable 函数,协调 Slot Table 读写
- Slot Table 以 Groups 和 Slots 存储组合状态
- Applier 将组合变更转换为具体的 Node Tree 操作
- Node Tree 是最终的渲染目标(如 LayoutNode)
- Compose Runtime 是通用的,可用于非 UI 场景
- 跳过机制 (Skipping) 是性能优化的关键
理解 Compose Runtime 的工作原理,能够帮助你:
- 写出更高效的 Composable 函数
- 理解为什么某些代码会触发重组
- debug 复杂的重组问题
- 扩展 Compose 到新的应用场景