一、前言
SlotTable是Compose的核心数据结构,它解决了声明式UI中的关键问题。掌握了SlotTable,就掌握了Compose的核心原理。
二、小案例
我们以一个简单的计数器为例,分别用传统函数和Compose函数来说明SlotTable的作用。
2、1 传统函数(无状态,无法记忆)
在传统函数中,函数调用时创建局部变量,调用结束就销毁。无法在多次调用中保持状态。
kotlin
fun traditionalCounter() {
var count = 0 // 每次调用都会重新初始化为0
count++
println("Count: $count")
}
// 多次调用
traditionalCounter() // 输出: Count: 1
traditionalCounter() // 输出: Count: 1 (状态没有保持)
2、2 传统函数(使用外部变量,但无法与节点树关联)
我们可以使用外部变量来保持状态,但如果有多个实例,它们会共享同一个状态,并且无法与特定的UI组件实例绑定。
kotlin
var globalCount = 0
fun traditionalCounterWithGlobal() {
globalCount++
println("Count: $globalCount")
}
// 多次调用
traditionalCounterWithGlobal() // 输出: Count: 1
traditionalCounterWithGlobal() // 输出: Count: 2
// 但是如果有多个计数器,它们都会修改同一个变量,无法独立。
2、3 Compose函数(使用SlotTable记忆状态):
在Compose中,我们使用remember来将状态与调用点(在SlotTable中的位置)关联起来。
kotlin
@Composable
fun ComposableCounter() {
// 这里的count状态会被存储在SlotTable中,与当前Composable在UI树中的位置关联
val count by remember { mutableStateOf(0) }
Button(onClick = { /* 如何增加?这里先不写,下面解释 */ }) {
Text("Count: $count")
}
}
但是,为了更新状态,我们需要一个事件,我们修改一下:
kotlin
@Composable
fun ComposableCounter() {
// 使用remember将状态存储在SlotTable中
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
现在,当我们点击按钮时,状态改变,触发重组。在重组时,remember会从SlotTable中读取之前存储的值(而不是重新初始化为0),所以状态得以保持,并且每次点击都会增加。
传统函数没有"记忆",但Compose需要:
- 记住状态 -
remember的值在重组间保持。 - 记住 UI 结构 - 知道哪些组件存在,它们的顺序。
- 比较差异 - 重组时知道什么变了,什么没变。
- 高效更新 - 只更新真正需要更新的部分。
SlotTable就是用来解决上面4个问题的。
三、SlotTable的数据结构
3、1 双数组设计
SlotTable有两个核心的数据结构,一个是groups,用来存储所有组的元数据,是一个扁平化的树结构。另一个是slots,用来存储实际数据(State、LayoutNode、RecomposeScope、CompositionLocal、remember等)。
kotlin
// SlotTable.kt
internal class SlotTable : CompositionData, Iterable<CompositionGroup> {
/**
* groups 数组存储组的元信息
* 每个组占用 5 个 Int 元素(Group_Fields_Size = 5)
*/
var groups = IntArray(0)
private set
var groupsSize = 0
private set
/**
* slots数组存储实际数据
* 如 remember 的值、RecomposeScopeImpl、Node 等
*/
var slots = Array<Any?>(0) { null }
private set
var slotsSize = 0
private set
}
3、2 Group的结构
每个组在groups数组中占用5个连续的Int:
| 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| key | groupInfo(flags) | parentAnchor | size | dataAnchor |
- key: 组的唯一标识符(由编译器生成)
- groupInfo: 包含多少个子节点和一些标志位(是否是节点、是否有Aux、是否有ObjectKey等)
- parentAnchor: 父节点的位置(用于构建树形结构)
- size: 组的大小(包含所有子组)
- dataAnchor: 指向slots数组中的位置,找到这个组对应的实际数据(remember的值、节点对象等)
GroupInfo位布局:
| 位 | 含义 |
|---|---|
| 30 | isNode - 是否是节点Group |
| 29 | hasObjectKey - 是否有对象类型的key |
| 28 | hasAux - 是否有辅助数据 |
| 27 | mark - 标记位 |
| 26 | containsMark - 是否包含标记 |
| 0-25 | nodeCount - 节点计数 |
这样设计的目的:
- 构建一个扁平化的树结构,访问更加快。
- 用5个Int描述一个组,省内存。
- 通过key匹配,重组时对比新旧状态。
- 知道父节点的位置和组的大小,可以快速的找到下一个组nextGroup = currentGroup + size,也可以快速的找到下一个父节点:parent = groups[currentGroup * 5 + 2]
请牢记,groups数组的结构、slots数组的结构以及这样设计的目的。
3、3 数据布局示例
假设有如下Compose函数
kotlin
@Composable
fun App() {
var show by remember { mutableStateOf(true) }
Column {
Text("Hello")
}
}
SlotTable布局:
groups数组就会变成这样(每5个Int为一组):
| App组元信息 ( 5个int) | remember组 (5个int) | Column组 (5个int) | Text组 (5个int) |
|---|
slots数组就会变成这样:
| App的RecomposeScope | MutableState(true) | Column的Node | "Hello"字符串 |
|---|
接下来用一个Compose函数,深入源码,去看下groups数组和slots数组是如何填充数据的。
四、源代码与编译器输出
经典的计数器累加例子
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
Count()
}
}
}
@Composable
fun Count() {
var content by remember { mutableStateOf(0) }
Button({
content++
}, content = {
Text(content.toString())
})
}
编译后Count函数变成了下面的代码,Count函数本来没有参数,编译后Count函数却有两个参数。
Java
public static final void Count(@Nullable Composer $composer, int $changed) {
// ============================================================
// 步骤1: 启动 RestartGroup
// startRestartGroup 会创建一个新的 Group 并记录 RecomposeScope
// key = -1491082337 是编译器为 Count 函数生成的唯一标识
// ============================================================
$composer = $composer.startRestartGroup(-1491082337);
// 记录源代码位置信息(用于调试和工具)
ComposerKt.sourceInformation($composer, "C(Count)20@464L30,24@544L40,22@500L85:Demo.kt#bw2bq5");
// ============================================================
// 步骤2: 跳过检查
// 如果 $changed == 0 且可以跳过,则跳过整个组合
// ============================================================
if ($changed == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
// 开始跟踪(用于性能分析)
if (ComposerKt.isTraceInProgress()) {
ComposerKt.traceEventStart(-1491082337, $changed, -1, "com.example.kmp.Count (Demo.kt:18)");
}
// ============================================================
// 步骤3: remember 块 - 创建并缓存 MutableState
// ============================================================
boolean var3 = false;
int $i$f$remember = 0;
// sourceInformationMarkerStart 不创建 Group,只是标记源代码位置
ComposerKt.sourceInformationMarkerStart($composer, -492369756, "CC(remember)N(calculation):Composables.kt#9igjgp");
boolean invalid$iv$iv = (boolean)0;
int $i$f$cache = 0;
// ============================================================
// 步骤4: 读取已缓存的值
// rememberedValue() 从当前 slot 读取之前存储的值
// 如果是首次组合,返回 Composer.Empty
// ============================================================
Object it$iv$iv = $composer.rememberedValue();
int var9 = 0;
Object var10000;
// ============================================================
// 步骤5: 判断是否需要创建新值
// 如果读取到 Empty,说明是首次组合,需要创建新的 MutableState
// ============================================================
if (it$iv$iv == Composer.Companion.getEmpty()) {
int var10 = 0;
// 创建 MutableState<Int>,初始值为 0
Object value$iv$iv = SnapshotStateKt.mutableStateOf$default(0, (SnapshotMutationPolicy)null, 2, (Object)null);
// 将新创建的值存储到 slot
$composer.updateRememberedValue(value$iv$iv);
var10000 = value$iv$iv;
} else {
// 重组时直接使用缓存的值
var10000 = it$iv$iv;
}
Object var11 = var10000;
ComposerKt.sourceInformationMarkerEnd($composer);
// content$delegate 就是 remember { mutableStateOf(0) } 的结果
final MutableState content$delegate = (MutableState)var11;
// ============================================================
// 步骤6: 为 onClick lambda 创建 ReplaceGroup
// startReplaceGroup 创建一个可替换的 Group
// key = -548075111 是编译器生成的唯一标识
// ============================================================
$composer.startReplaceGroup(-548075111);
// 检查 content$delegate 是否发生变化
boolean $this$cache$iv$iv = $composer.changed(content$delegate);
invalid$iv$iv = (boolean)0;
Object it$iv = $composer.rememberedValue();
int var16 = 0;
// ============================================================
// 步骤7: 缓存 onClick lambda
// 如果依赖没变且之前有缓存,使用缓存的 lambda
// 否则创建新的 lambda 并缓存
// ============================================================
if (!$this$cache$iv$iv && it$iv != Composer.Companion.getEmpty()) {
var10000 = it$iv;
} else {
var9 = 0;
// DemoKt::Count$lambda$4$lambda$3 是编译器生成的 lambda 引用
Object value$iv = DemoKt::Count$lambda$4$lambda$3;
$composer.updateRememberedValue(value$iv);
var10000 = value$iv;
}
Function0 var13 = (Function0)var10000;
// 结束 ReplaceGroup
$composer.endReplaceGroup();
// ============================================================
// 步骤8: 调用 Button Composable
// Button 内部会创建自己的 Groups
// 最后一个参数是 content lambda,使用 rememberComposableLambda 包装
// ============================================================
ButtonKt.Button(
var13, // onClick
(Modifier)null, // modifier
false, // enabled
(Shape)null, // shape
(ButtonColors)null, // colors
(ButtonElevation)null, // elevation
(BorderStroke)null, // border
(PaddingValues)null, // contentPadding
(MutableInteractionSource)null, // interactionSource
// content lambda - 使用 rememberComposableLambda 缓存
(Function3)ComposableLambdaKt.rememberComposableLambda(
1836707247, // key
true, // tracked
new Function3() { // lambda 实现
@Composable
public final void invoke(RowScope $this$Button, Composer $composer, int $changed) {
// ... Text(content.toString()) ...
}
// ...
},
$composer,
54
),
$composer,
805306368, // $changed
510 // $default
);
if (ComposerKt.isTraceInProgress()) {
ComposerKt.traceEventEnd();
}
}
// ============================================================
// 步骤9: 结束 RestartGroup 并注册重组回调
// endRestartGroup 返回 ScopeUpdateScope,用于注册重组 lambda
// ============================================================
ScopeUpdateScope var21 = $composer.endRestartGroup();
if (var21 != null) {
// 注册重组回调:当需要重组时,调用 Count$lambda$5
var21.updateScope(DemoKt::Count$lambda$5);
}
}
// 重组时调用的 lambda
private static final Unit Count$lambda$5(int $$changed, Composer $composer, int $force) {
Count($composer, RecomposeScopeImplKt.updateChangedFlags($$changed | 1));
return Unit.INSTANCE;
}
Count函数在调用之前,经历了一系列的函数调用,下面是调用链。
scss
setContent { Count() }
↓
ComponentActivity.setContent // ComponentActivity.kt
↓
ComposeView.setContent // ComposeView.kt
↓
ComposeView.createComposition // ComposeView.kt
↓
ComposeView.ensureCompositionCreated // ComposeView.kt
↓
AbstractComposeView.setContent // Wrapper_android.kt
↓
AbstractComposeView.doSetContent // Wrapper_android.kt
↓
WrappedComposition.setContent // WrappedComposition.kt
↓
CompositionImpl.setContent() // Composition.kt
↓
CompositionImpl.composeInitial() // Composition.kt
↓
Recomposer.composeInitial() // Recomposer.kt
↓
Recomposer.composing() // Recomposer.kt
↓
composition.composeContent(content) // Composition.kt
↓
ComposerImpl.composeContent() // Composer.kt
↓
ComposerImpl.composeContent(invalidations, content) // Composer.kt
↓
ComposerImpl.doCompose(invalidations, content) // Composer.kt
↓
startRoot() // 创建Root Group
↓
startGroup(invocationKey, invocation) // 创建 Invocation Group
↓
invokeComposable(this, content) // 调用用户的 Composable(如 Count)
↓
Count($composer, 0) // Count函数开始执行
doCompose
kotlin
private fun doCompose(
invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
content: (@Composable () -> Unit)?,
) {
runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
val observer = observerHolder.current()
trace("Compose:recompose") {
compositionToken = currentSnapshot().snapshotId.hashCode()
providerUpdates = null
updateComposerInvalidations(invalidationsRequested)
nodeIndex = 0
var complete = false
isComposing = true
observer?.onBeginComposition(composition)
try {
// 创建第一个组
startRoot()
// vv Experimental for forced
val savedContent = nextSlot()
if (savedContent !== content && content != null) {
// 保存setContent方法传入lambda到slots数组
updateValue(content as Any?)
}
// ^^ Experimental for forced
// Ignore reads of derivedStateOf recalculations
observeDerivedStateRecalculations(derivedStateObserver) {
if (content != null) {
// 创建key为200的组
startGroup(invocationKey, invocation)
// 调用Count函数
invokeComposable(this, content)
// 结束组
endGroup()
} else if (
(forciblyRecompose || providersInvalid) &&
savedContent != null &&
savedContent != Composer.Empty
) {
startGroup(invocationKey, invocation)
@Suppress("UNCHECKED_CAST")
invokeComposable(this, savedContent as @Composable () -> Unit)
endGroup()
} else {
skipCurrentGroup()
}
}
// 组合完成
endRoot()
complete = true
} catch (e: Throwable) {
throw e.attachComposeStackTrace { currentStackTrace() }
} finally {
observer?.onEndComposition(composition)
isComposing = false
invalidations.clear()
if (!complete) abortRoot()
createFreshInsertTable()
}
}
}
当startRoot()、updateValue(content as Any?)、startGroup(invocationKey, invocation)执行完成后,groups数组就变成了这样。
vbnet
Group 0: Root Group (key = 100)
├── Group 1: KeyHash Group (key = parentContext.compositeKeyHashCode)
└── slots[0]: content lambda (setContent 传入的 lambda)
└── Group 2: Invocation Group (key = invocationKey)
从startRoot开始重点解析下组是如何创建的。
startRoot
startRoot用于创建groups数组的第一个组。
kotlin
@OptIn(InternalComposeApi::class)
private fun startRoot() {
rGroupIndex = 0
reader = slotTable.openReader() // 打开 SlotReader
// =========================================
// 创建 Root Group(key = rootKey = 100)
// 这是 groups 数组中的第一个组
// =========================================
startGroup(rootKey) // rootKey = 100
// 处理 CompositionLocal
parentContext.startComposing()
val parentProvider = parentContext.getCompositionLocalScope()
providersInvalidStack.push(providersInvalid.asInt())
providersInvalid = changed(parentProvider)
providerCache = null
// 处理强制重组作用域
if (!forceRecomposeScopes) {
forceRecomposeScopes = parentContext.collectingParameterInformation
}
// 处理源码标记
if (!sourceMarkersEnabled) {
sourceMarkersEnabled = parentContext.collectingSourceInformation
}
rootProvider = if (sourceMarkersEnabled) {
@Suppress("UNCHECKED_CAST")
parentProvider.putValue(
LocalCompositionErrorContext as CompositionLocal<Any?>,
StaticValueHolder(errorContext),
)
} else {
parentProvider
}
rootProvider.read(LocalInspectionTables)?.let {
it.add(compositionData)
parentContext.recordInspectionTable(it)
}
// =========================================
// 创建 KeyHash Group
// 这是 Root Group 的子 Group
// =========================================
startGroup(parentContext.compositeKeyHashCode.hashCode())
}
startRoot调用startGroup(rootKey)创建第一个组,key为100,startGroup调用start。
start
kotlin
private fun start(key: Int, objectKey: Any?, kind: GroupKind, data: Any?) {
// isNode为false,因为数组的第一个组不是节点,Text、Colimn、Button是节点
val isNode = kind.isNode
if (inserting) {
// 首次组合,inserting为true
reader.beginEmpty()
val startIndex = writer.currentGroup
when {
isNode -> writer.startNode(key, Composer.Empty)
data != null -> writer.startData(key, objectKey ?: Composer.Empty, data)
// 调用SlotTable的startGroup
else -> writer.startGroup(key, objectKey ?: Composer.Empty)
}
pending?.let { pending ->
val insertKeyInfo =
KeyInfo(
key = key,
objectKey = -1,
location = insertedGroupVirtualIndex(startIndex),
nodes = -1,
index = 0,
)
pending.registerInsert(insertKeyInfo, nodeIndex - pending.startIndex)
pending.recordUsed(insertKeyInfo)
}
enterGroup(isNode, null)
return
}
// 省略后面的代码,重组的时候才会执行后面的代码
}
SlotTable的startGroup
kotlin
private fun startGroup(key: Int, objectKey: Any?, isNode: Boolean, aux: Any?) {
val previousParent = parent
val inserting = insertCount > 0
nodeCountStack.push(nodeCount)
currentGroupEnd =
if (inserting) {
val current = currentGroup
val newCurrentSlot = groups.dataIndex(groupIndexToAddress(current))
// 插入一个组
insertGroups(1)
currentSlot = newCurrentSlot
currentSlotEnd = newCurrentSlot
val currentAddress = groupIndexToAddress(current)
val hasObjectKey = objectKey !== Composer.Empty
val hasAux = !isNode && aux !== Composer.Empty
val dataAnchor =
dataIndexToDataAnchor(
index = newCurrentSlot,
gapLen = slotsGapLen,
gapStart = slotsGapStart,
capacity = slots.size,
)
.let { anchor ->
if (anchor >= 0 && slotsGapOwner < current) {
val slotsSize = slots.size - slotsGapLen
-(slotsSize - anchor + 1)
} else anchor
}
// 初始化组
groups.initGroup(
address = currentAddress,
key = key,
isNode = isNode,
hasDataKey = hasObjectKey,
hasData = hasAux,
parentAnchor = parent,
dataAnchor = dataAnchor,
)
val dataSlotsNeeded =
(if (isNode) 1 else 0) + (if (hasObjectKey) 1 else 0) + (if (hasAux) 1 else 0)
if (dataSlotsNeeded > 0) {
insertSlots(dataSlotsNeeded, current)
val slots = slots
var currentSlot = currentSlot
if (isNode) slots[currentSlot++] = aux
if (hasObjectKey) slots[currentSlot++] = objectKey
if (hasAux) slots[currentSlot++] = aux
this.currentSlot = currentSlot
}
nodeCount = 0
val newCurrent = current + 1
this.parent = current
this.currentGroup = newCurrent
if (previousParent >= 0) {
sourceInformationOf(previousParent)?.reportGroup(this, current)
}
newCurrent
} else {
// 省略后面的代码,重组的时候才会执行后面的代码
}
}
insertGroups
insertGroups其实就是使用GapBuffer,如果你对GapBuffer不熟悉,请阅读Compose原理二之GapBuffer
kotlin
private fun insertGroups(size: Int) {
if (size > 0) {
val currentGroup = currentGroup
// 将间隙移动到目标位置
moveGroupGapTo(currentGroup)
val gapStart = groupGapStart
var gapLen = groupGapLen
val oldCapacity = groups.size / Group_Fields_Size
val oldSize = oldCapacity - gapLen
if (gapLen < size) {
// 间隙用完,扩容2倍
val groups = groups
// Double the size of the array, but at least MinGrowthSize and >= size
val newCapacity = max(max(oldCapacity * 2, oldSize + size), MinGroupGrowthSize)
val newGroups = IntArray(newCapacity * Group_Fields_Size)
val newGapLen = newCapacity - oldSize
val oldGapEndAddress = gapStart + gapLen
val newGapEndAddress = gapStart + newGapLen
// 复制数组
groups.copyInto(
destination = newGroups,
destinationOffset = 0,
startIndex = 0,
endIndex = gapStart * Group_Fields_Size,
)
groups.copyInto(
destination = newGroups,
destinationOffset = newGapEndAddress * Group_Fields_Size,
startIndex = oldGapEndAddress * Group_Fields_Size,
endIndex = oldCapacity * Group_Fields_Size,
)
// 使用扩容后的groups数组
this.groups = newGroups
// 更新间隙长度
gapLen = newGapLen
}
// Move the currentGroupEnd to account for inserted groups.
val currentEnd = currentGroupEnd
if (currentEnd >= gapStart) this.currentGroupEnd = currentEnd + size
// 更新间隙的起始位置和间隙的长度
this.groupGapStart = gapStart + size
this.groupGapLen = gapLen - size
// 省略后面的代码
}
}
groups.initGroup
groups数组中每5个Int为一组,initGroup就是用来初始化5个int。
kotlin
private fun IntArray.initGroup(
address: Int,
key: Int,
isNode: Boolean,
hasDataKey: Boolean,
hasData: Boolean,
parentAnchor: Int,
dataAnchor: Int,
) {
val arrayIndex = address * Group_Fields_Size // 0 * 5 = 0
this[arrayIndex + Key_Offset] = key // groups[0] = 100
this[arrayIndex + GroupInfo_Offset] = // groups[1] = 0 (无标志位)
(isNode.toBit() shl NodeBit_Shift) or
(hasDataKey.toBit() shl ObjectKey_Shift) or
(hasData.toBit() shl Aux_Shift)
this[arrayIndex + ParentAnchor_Offset] = parentAnchor // groups[2] = -1
this[arrayIndex + Size_Offset] = 0 // groups[3] = 0 (稍后更新)
this[arrayIndex + DataAnchor_Offset] = dataAnchor // groups[4] = 0
}
保存setContent方法传入的lambda到slots数组
startRoot执行完成后,保存setContent方法传入的lambda到slots数组
kotlin
val savedContent = nextSlot()
// 保存setContent方法传入lambda到slots数组
if (savedContent !== content && content != null) {
updateValue(content as Any?)
}
kotlin
internal fun updateValue(value: Any?) {
if (inserting) {
// 写入到slots数组
writer.update(value)
} else {
// 省略后面的代码
}
invokeComposable(this, content)
编译后的Count函数有来两个参数,realFn就是调用Count函数,传入compose对象和参数1,参数1表示没有发生改变。
kotlin
internal actual fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {
@Suppress("UNCHECKED_CAST") val realFn = composable as Function2<Composer, Int, Unit>
// 调用count函数
realFn(composer, 1)
}
到此,count函数开始执行,在执行之前,groups数组已经有这些数据了。
vbnet
Group 0: Root Group (key = 100)
├── Group 1: KeyHash Group (key = parentContext.compositeKeyHashCode)
└── slots[0]: content lambda (setContent 传入的 lambda)
└── Group 2: Invocation Group (key = invocationKey)
编译后的Count函数是这样的:
Java
public static final void Count(@Nullable Composer $composer, int $changed) {
// ============================================================
// 步骤1: 启动 RestartGroup
// startRestartGroup 会创建一个新的 Group 并记录 RecomposeScope
// key = -1491082337 是编译器为 Count 函数生成的唯一标识
// ============================================================
$composer = $composer.startRestartGroup(-1491082337);
// 记录源代码位置信息(用于调试和工具)
ComposerKt.sourceInformation($composer, "C(Count)20@464L30,24@544L40,22@500L85:Demo.kt#bw2bq5");
// ============================================================
// 步骤2: 跳过检查
// 如果 $changed == 0 且可以跳过,则跳过整个组合
// ============================================================
if ($changed == 0 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
// 开始跟踪(用于性能分析)
if (ComposerKt.isTraceInProgress()) {
ComposerKt.traceEventStart(-1491082337, $changed, -1, "com.example.kmp.Count (Demo.kt:18)");
}
// ============================================================
// 步骤3: remember 块 - 创建并缓存 MutableState
// ============================================================
boolean var3 = false;
int $i$f$remember = 0;
// sourceInformationMarkerStart 不创建 Group,只是标记源代码位置
ComposerKt.sourceInformationMarkerStart($composer, -492369756, "CC(remember)N(calculation):Composables.kt#9igjgp");
boolean invalid$iv$iv = (boolean)0;
int $i$f$cache = 0;
// ============================================================
// 步骤4: 读取已缓存的值
// rememberedValue() 从当前 slot 读取之前存储的值
// 如果是首次组合,返回 Composer.Empty
// ============================================================
Object it$iv$iv = $composer.rememberedValue();
int var9 = 0;
Object var10000;
// ============================================================
// 步骤5: 判断是否需要创建新值
// 如果读取到 Empty,说明是首次组合,需要创建新的 MutableState
// ============================================================
if (it$iv$iv == Composer.Companion.getEmpty()) {
int var10 = 0;
// 创建 MutableState<Int>,初始值为 0
Object value$iv$iv = SnapshotStateKt.mutableStateOf$default(0, (SnapshotMutationPolicy)null, 2, (Object)null);
// 将新创建的值存储到 slot
$composer.updateRememberedValue(value$iv$iv);
var10000 = value$iv$iv;
} else {
// 重组时直接使用缓存的值
var10000 = it$iv$iv;
}
Object var11 = var10000;
ComposerKt.sourceInformationMarkerEnd($composer);
// content$delegate 就是 remember { mutableStateOf(0) } 的结果
final MutableState content$delegate = (MutableState)var11;
// ============================================================
// 步骤6: 为 onClick lambda 创建 ReplaceGroup
// startReplaceGroup 创建一个可替换的 Group
// key = -548075111 是编译器生成的唯一标识
// ============================================================
$composer.startReplaceGroup(-548075111);
// 检查 content$delegate 是否发生变化
boolean $this$cache$iv$iv = $composer.changed(content$delegate);
invalid$iv$iv = (boolean)0;
Object it$iv = $composer.rememberedValue();
int var16 = 0;
// ============================================================
// 步骤7: 缓存 onClick lambda
// 如果依赖没变且之前有缓存,使用缓存的 lambda
// 否则创建新的 lambda 并缓存
// ============================================================
if (!$this$cache$iv$iv && it$iv != Composer.Companion.getEmpty()) {
var10000 = it$iv;
} else {
var9 = 0;
// DemoKt::Count$lambda$4$lambda$3 是编译器生成的 lambda 引用
Object value$iv = DemoKt::Count$lambda$4$lambda$3;
$composer.updateRememberedValue(value$iv);
var10000 = value$iv;
}
Function0 var13 = (Function0)var10000;
// 结束 ReplaceGroup
$composer.endReplaceGroup();
// ============================================================
// 步骤8: 调用 Button Composable
// Button 内部会创建自己的 Groups
// 最后一个参数是 content lambda,使用 rememberComposableLambda 包装
// ============================================================
ButtonKt.Button(
var13, // onClick
(Modifier)null, // modifier
false, // enabled
(Shape)null, // shape
(ButtonColors)null, // colors
(ButtonElevation)null, // elevation
(BorderStroke)null, // border
(PaddingValues)null, // contentPadding
(MutableInteractionSource)null, // interactionSource
// content lambda - 使用 rememberComposableLambda 缓存
(Function3)ComposableLambdaKt.rememberComposableLambda(
1836707247, // key
true, // tracked
new Function3() { // lambda 实现
@Composable
public final void invoke(RowScope $this$Button, Composer $composer, int $changed) {
// ... Text(content.toString()) ...
}
// ...
},
$composer,
54
),
$composer,
805306368, // $changed
510 // $default
);
if (ComposerKt.isTraceInProgress()) {
ComposerKt.traceEventEnd();
}
}
// ============================================================
// 步骤9: 结束 RestartGroup 并注册重组回调
// endRestartGroup 返回 ScopeUpdateScope,用于注册重组 lambda
// ============================================================
ScopeUpdateScope var21 = $composer.endRestartGroup();
if (var21 != null) {
// 注册重组回调:当需要重组时,调用 Count$lambda$5
var21.updateScope(DemoKt::Count$lambda$5);
}
}
// 重组时调用的 lambda
private static final Unit Count$lambda$5(int $$changed, Composer $composer, int $force) {
Count($composer, RecomposeScopeImplKt.updateChangedFlags($$changed | 1));
return Unit.INSTANCE;
}
下面解析Count函数
startRestartGroup(-1491082337)
startRestartGroup用于创建可以重复执行的组,只要调用了startRestartGroup,函数就可以被重新执行。
kotlin
@ComposeCompilerApi
override fun startRestartGroup(key: Int): Composer {
// 创建组
startReplaceGroup(key)
// 保存重组作用域对象
addRecomposeScope()
return this
}
startReplaceGroup就是创建key为-1491082337,writer.startGroup在之前讲解过,这里就不重复了。
kotlin
override fun startReplaceGroup(key: Int) {
val reader = reader
if (inserting) {
reader.beginEmpty()
// 创建组,key为编译器生成的-1491082337
writer.startGroup(key, Composer.Empty)
enterGroup(false, null)
return
}
}
addRecomposeScope
创建重组作用域对象,放入到失效栈中,保存到slots数组。每个可以重新执行的函数,都会创建一个重组作用域对象。创建重组作用域里面有个block属性,block属性就是需要重新执行的Count函数。重组的时候会取出重组作用域对象,调用block,重新执行Count函数。
kotlin
private fun addRecomposeScope() {
// 组合的时候为true,也就是第一次执行count函数为false。重组的时候为false,重新执行count函数为false
if (inserting) {
// 创建重组作用域对象
val scope = RecomposeScopeImpl(composition as CompositionImpl)
invalidateStack.push(scope)
// 保存到slots数组
updateValue(scope)
enterRecomposeScope(scope)
} else {
// 省略后面的代码,重组的时候会执行后面的代码
}
}
此时数组状态
vbnet
Group 0: Root Group (key = 100)
├── Group 1: KeyHash Group (key = parentContext.compositeKeyHashCode)
└── slots[0]: content lambda (setContent 传入的 lambda)
└── Group 2: Invocation Group (key = invocationKey)
└── Group 3: key = -1491082337
└── slots[1]: RecomposeScopeImpl // 重组作用域对象
remember { mutableStateOf(0) }
remember不创建新的组,它通过 rememberedValue() 和 updateRememberedValue() 操作当前组的slot数组。
kotlin
// 首次组合时 rememberedValue() 返回Empty
Object it$iv$iv = $composer.rememberedValue(); // 返回 Composer.Empty
// 因为是 Empty,创建新的MutableState
Object value$iv$iv = SnapshotStateKt.mutableStateOf$default(0, null, 2, null);
// 存储到 slot
$composer.updateRememberedValue(value$iv$iv);
updateRememberedValue 调用 updateCachedValue:
kotlin
override fun updateRememberedValue(value: Any?) = updateCachedValue(value)
// Composer.kt 第 2213-2224 行
internal fun updateCachedValue(value: Any?) {
val toStore = if (value is RememberObserver) {
// MutableState 不是 RememberObserver,直接使用
// ...
} else value
updateValue(toStore) // 存储到 slot
}
此时数组状态
ini
Group 0: Root Group (key = 100)
├── Group 1: KeyHash Group (key = parentContext.compositeKeyHashCode)
└── slots[0]: content lambda (setContent 传入的 lambda)
└── Group 2: Invocation Group (key = invocationKey)
└── Group 3: key = -1491082337
└── slots[1]: RecomposeScopeImpl // 重组作用域对象
└── slots[2]: MutableState<Int>(value=0) // remember 的值
startReplaceGroup(-548075111) - onClick lambda缓存
为onClick lambda创建group,key为-548075111
bash
$composer.startReplaceGroup(-548075111);
endReplaceGroup() - 结束 onClick 缓存组
scss
// SlotWriter.endGroup() 更新 Group 大小
groups.updateGroupSize(groupAddress, newGroupSize) // Size = 1
groups.updateNodeCount(groupAddress, newNodes) // nodeCount = 0
此时数组状态
ini
Group 0: Root Group (key = 100)
├── Group 1: KeyHash Group (key = parentContext.compositeKeyHashCode)
└── slots[0]: content lambda (setContent 传入的 lambda)
└── Group 2: Invocation Group (key = invocationKey)
└── Group 3: key = -1491082337
└── slots[1]: RecomposeScopeImpl // 重组作用域对象
└── slots[2]: MutableState<Int>(value=0) // remember 的值
└── Group 4: key = -548075111
└── slots[3]: content$delegate (MutableState 引用,用于 changed 检测)
└── slots[4]: Function0 (onClick lambda)
Button和Text组件
最终会调用到ReusableComposeNode,factory其实就是用来创建LayoutNode对象。
kotlin
public inline fun <T : Any, reified E : Applier<*>> ReusableComposeNode(
noinline factory: () -> T,
update: @DisallowComposableCalls Updater<T>.() -> Unit,
) {
if (currentComposer.applier !is E) invalidApplier()
// 创建可复用的节点
currentComposer.startReusableNode()
if (currentComposer.inserting) {
// 组合的时候,创建节点
currentComposer.createNode(factory)
} else {
// 重组的时候,直接复用节点
currentComposer.useNode()
}
Updater<T>(currentComposer).update()
// 结束节点
currentComposer.endNode()
}
startReusableNode
创建可复用的节点,key为125。
kotlin
override fun startReusableNode() {
// 创建可复用的节点,key为125
start(nodeKey, null, GroupKind.ReusableNode, null)
nodeExpected = true
}
private fun start(key: Int, objectKey: Any?, kind: GroupKind, data: Any?) {
// 是节点
val isNode = kind.isNode
if (inserting) {
reader.beginEmpty()
val startIndex = writer.currentGroup
when {
// 创建节点
isNode -> writer.startNode(key, Composer.Empty)
data != null -> writer.startData(key, objectKey ?: Composer.Empty, data)
else -> writer.startGroup(key, objectKey ?: Composer.Empty)
}
pending?.let { pending ->
val insertKeyInfo =
KeyInfo(
key = key,
objectKey = -1,
location = insertedGroupVirtualIndex(startIndex),
nodes = -1,
index = 0,
)
pending.registerInsert(insertKeyInfo, nodeIndex - pending.startIndex)
pending.recordUsed(insertKeyInfo)
}
enterGroup(isNode, null)
return
}
// 省略后面的代码
}
writer.startNode(key, Composer.Empty)
还是调用startGroup,只不过isNode为true
kotlin
fun startNode(key: Int, objectKey: Any?) =
startGroup(key, objectKey, isNode = true, aux = Composer.Empty)
此时数组状态
ini
Group 0: Root Group (key = 100)
├── Group 1: KeyHash Group (key = parentContext.compositeKeyHashCode)
└── slots[0]: content lambda (setContent 传入的 lambda)
└── Group 2: Invocation Group (key = invocationKey)
└── Group 3: key = -1491082337
└── slots[1]: RecomposeScopeImpl // 重组作用域对象
└── slots[2]: MutableState<Int>(value=0) // remember 的值
└── Group 4: key = -548075111
└── slots[3]: content$delegate (MutableState 引用,用于 changed 检测)
└── slots[4]: Function0 (onClick lambda)
└── Group 5: key = 125,groupinfo里面记录的isNode为true
createNode
在首次组合时调用,不会立即创建节点,而是将创建操作记录到FixupList。
kotlin
@Suppress("UNUSED")
override fun <T> createNode(factory: () -> T) {
validateNodeExpected()
runtimeCheck(inserting) { "createNode() can only be called when inserting" }
// 插入的位置
val insertIndex = parentStateStack.peek()
// 指向slots数组中的位置
val groupAnchor = writer.anchor(writer.parent)
groupNodeCount++
// 记录到 FixupList,延迟执行
insertFixups.createAndInsertNode(factory, insertIndex, groupAnchor)
}
FixupList.createAndInsertNode方法就是将节点放到operations里面,InsertNodeFixup会调用Applier的insertTopDown方法,PostInsertNodeFixup会调用Applier的insertBottomUp方法。关于Applier,后面会讲。
kotlin
fun createAndInsertNode(factory: () -> Any?, insertIndex: Int, groupAnchor: Anchor) {
// 1. InsertNodeFixup: 创建节点并调用Applier的insertTopDown方法
operations.push(InsertNodeFixup) {
setObject(InsertNodeFixup.Factory, factory)
setInt(InsertNodeFixup.InsertIndex, insertIndex)
setObject(InsertNodeFixup.GroupAnchor, groupAnchor)
}
// 2. PostInsertNodeFixup: 子节点处理完后调用Applier的insertBottomUp方法
pendingOperations.push(PostInsertNodeFixup) {
setInt(PostInsertNodeFixup.InsertIndex, insertIndex)
setObject(PostInsertNodeFixup.GroupAnchor, groupAnchor)
}
}
Operations内部有个集合,调用Operations.push就是将节点保存到集合。什么时候取出节点呢?后面再讲。
kotlin
inline fun push(operation: Operation, args: WriteScope.() -> Unit) {
contract { callsInPlace(args, EXACTLY_ONCE) }
@OptIn(InternalComposeApi::class)
// 保存到集合
pushOp(operation)
WriteScope(this).args()
ensureAllArgumentsPushedFor(operation)
}
在组合过程中,所有对SlotTable和节点树的修改都被记录到ChangeList中:
主要 Operation 类型
| Operation | 作用 | 目标 |
|---|---|---|
InsertNodeFixup |
创建节点并 insertTopDown | Applier |
PostInsertNodeFixup |
执行 insertBottomUp | Applier |
UpdateNode |
更新节点属性 | Applier |
RemoveNode |
从树中移除节点 | Applier |
MoveNode |
移动节点位置 | Applier |
Ups / Downs |
导航 Applier 的当前位置 | Applier |
InsertSlots |
将 Slots 插入 SlotTable | SlotWriter |
RemoveCurrentGroup |
删除当前 Group | SlotWriter |
MoveCurrentGroup |
移动 Group | SlotWriter |
UpdateValue |
更新 Slot 值 | SlotWriter |
Operation 的执行目标
每个 Operation 的 execute 方法接收三个关键参数:
kotlin
protected abstract fun OperationArgContainer.execute(
applier: Applier<*>, // 用于操作节点树
slots: SlotWriter, // 用于操作 SlotTable
rememberManager: RememberManager, // 用于生命周期管理
errorContext: OperationErrorContext?,
)
组合阶段不直接修改 LayoutNode树,而是将所有操作记录到ChangeList。这样做的好处:
- 可以批量应用变更,减少 UI 抖动。
- 支持取消和恢复。
endNode
kotlin
override fun endNode() = end(isNode = true)
private fun end(isNode: Boolean) {
// 省略部分代码,直接看关键的recordInsert
recordInsert(insertAnchor)
}
private fun recordInsert(anchor: Anchor) {
if (insertFixups.isEmpty()) {
changeListWriter.insertSlots(anchor, insertTable)
} else {
// 之前调用createNode的时候,节点被放到insertFixups,所以insertFixups不为空
changeListWriter.insertSlots(anchor, insertTable, insertFixups)
insertFixups = FixupList()
}
}
ChanheList.pushInsertSlots方法,fixups参数保存了之前创建的节点。创建的Button、Text等节点都被保存到了Operations里面。
kotlin
fun pushInsertSlots(anchor: Anchor, from: SlotTable, fixups: FixupList) {
operations.push(InsertSlotsWithFixups) {
setObjects(
InsertSlotsWithFixups.Anchor,
anchor,
InsertSlotsWithFixups.FromSlotTable,
from,
InsertSlotsWithFixups.Fixups,
fixups,
)
}
}
endRestartGroup
节点都创建完成后,执行endRestartGroup。在执行startRestartGroup的时候,创建了重组作用域对象,将对象放到了失效栈中。执行endRestartGroup,从失效栈中取出重组作用域对象,返回出去。
kotlin
override fun endRestartGroup(): ScopeUpdateScope? {
// 从失效栈中取出重组作用域对象
val scope = if (invalidateStack.isNotEmpty()) invalidateStack.pop() else null
if (scope != null) {
scope.requiresRecompose = false
exitRecomposeScope(scope)?.let { changeListWriter.endCompositionScope(it, composition) }
if (scope.resuming) {
scope.resuming = false
changeListWriter.endResumingScope(scope)
scope.reusing = false
if (scope.resetReusing) {
scope.resetReusing = false
reusing = false
}
}
}
val result =
if (scope != null && !scope.skipped && (scope.used || forceRecomposeScopes)) {
if (scope.anchor == null) {
scope.anchor =
if (inserting) {
writer.anchor(writer.parent)
} else {
reader.anchor(reader.parent)
}
}
scope.defaultsInvalid = false
scope
} else {
null
}
end(isNode = false)
// 返回重组作用域对象
return result
}
拿到重组作用域对象后,调用updateScope保存需要重新执行的函数,也就是Count函数。
kotlin
ScopeUpdateScope var21 = $composer.endRestartGroup();
if (var21 != null) { // 注册重组回调:当需要重组时,调用 Count$lambda$5
var21.updateScope(DemoKt::Count$lambda$5);
}
// 重组时调用的lambda
private static final Unit Count$lambda$5(int $$changed, Composer $composer, int $force) {
Count($composer, RecomposeScopeImplKt.updateChangedFlags($$changed | 1));
return Unit.INSTANCE;
}
updateScope方法仅仅是为block对象赋值,
kotlin
override fun updateScope(block: (Composer, Int) -> Unit) {
this.block = block
}
重组的时候会调用compose方法,进而调用block,Count函数就会重新执行。
kotlin
fun compose(composer: Composer) {
block?.invoke(composer, 1) ?: error("Invalid restart scope")
}
应用变更
Button、Text等节点都保存到了Operations的集合里面,什么时候取出来呢?让我们回到Recomposer.composeInitial方法。当Count函数执行完成后,会调用composition.applyChanges应用变更。
kotlin
internal override fun composeInitial(
composition: ControlledComposition,
content: @Composable () -> Unit,
) {
try {
composing(composition, null) { composition.composeContent(content) }
} catch (e: Throwable) {
if (newComposition) {
synchronized(stateLock) { unregisterCompositionLocked(composition) }
}
processCompositionError(e, composition, recoverable = true)
return
}
// 省略部分代码...
try {
// 应用变更
composition.applyChanges()
composition.applyLateChanges()
} catch (e: Throwable) {
processCompositionError(e)
return
}
if (!composerWasComposing) {
// Ensure that any state objects created during applyChanges are seen as changed
// if modified after this call.
Snapshot.notifyObjectsInitialized()
}
}
composition.applyChanges会调用到InsertNodeFixups的execute。在execute方法中创建LayoutNode对象,将LayoutNode对象保存到slots数组中。
kotlin
override fun OperationArgContainer.execute(
applier: Applier<*>,
slots: SlotWriter,
rememberManager: RememberManager,
errorContext: OperationErrorContext?,
) {
// 取出LayoutNode的构造方法,并且执行,创建LayoutNode对象
val node = getObject(Factory).invoke()
val groupAnchor = getObject(GroupAnchor)
val insertIndex = getInt(InsertIndex)
val nodeApplier = @Suppress("UNCHECKED_CAST") (applier as Applier<Any?>)
// 保存到slots数组中
slots.updateNode(groupAnchor, node)
nodeApplier.insertTopDown(insertIndex, node)
nodeApplier.down(node)
}
到此,Button、Text组件也被放到slots数组里面了,所有的组件最终都是LayoutNode对象。此时,数组就变成了这样:
ini
Group 0: Root Group (key = 100)
├── Group 1: KeyHash Group (key = parentContext.compositeKeyHashCode)
└── slots[0]: content lambda (setContent 传入的 lambda)
└── Group 2: Invocation Group (key = invocationKey)
└── Group 3: key = -1491082337
└── slots[1]: RecomposeScopeImpl // 重组作用域对象
└── slots[2]: MutableState<Int>(value=0) // remember 的值
└── Group 4: key = -548075111
└── slots[3]: content$delegate (MutableState 引用,用于 changed 检测)
└── slots[4]: Function0 (onClick lambda)
└── Group 5: key = 125,groupinfo里面记录的isNode为true
└── slots[5]: Button组件
└── slots[6]: Text组件
SlotTable映射到LayoutNode
LayoutNode树是实际的节点树,用于测量、布局和绘制。SlotTable结构的变化最终要映射到LayoutNode,通过Applier接口来调用到LayoutNode。Applier提供了从上到下、从下到上两种插入方式。
kotlin
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 apply(block: N.(Any?) -> Unit, value: Any?) // 应用属性更新
}
internal class UiApplier(root: LayoutNode) : AbstractApplier<LayoutNode>(root) {
/**
* 从上到下插入,方法空实现,不使用从上到下插入的方式
*/
override fun insertTopDown(index: Int, instance: LayoutNode) {
// Ignored.
}
/**
* 从下到上插入。
*/
override fun insertBottomUp(index: Int, instance: LayoutNode) {
// current就是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()
}
override fun onEndChanges() {
super.onEndChanges()
root.owner?.onEndApplyChanges()
}
override fun reuse() {
current.onReuse()
}
}
为什么使用从下到上插入?
less
从上到下插入: 从下到上插入:
1 2 3 1 2 3
R R R B B R
| | | | / \ |
B B B A A C B
/ / \ / \
A A C A C
对于 LayoutNode 树,当子节点插入时会通知所有祖先节点,如果使用从上到下插入:
- R 被通知 B 进入
- B 被通知 A 进入,R 被通知 A 进入
- B 被通知 C 进入,R 被通知 C 进入
- 共 5 次通知,呈指数增长
使用从下到上插入:
- B 被通知 A 进入
- B 被通知 C 进入
- R 被通知 B 进入
- 共 3 次通知,线性增长
因为子节点的变动会影响父节点的重新测量,从下到上插入可以避免影响太多的父节点,提高性能。
为什么要通过Applier接口调用到LayoutNode?这里开了个口子,UiApplier与LayoutNode只是安卓平台的对应实现,我们可以通过自定义Applier可以打造自己的渲染引擎。
insertBottomUp
insertBottomUp是从下往上插入,什么时候调用insertBottomUp?执行完InsertNodeFixups的execute后,还会执行PostInsertNodeFixups的execute,从slots数组中取出节点,把节点插入到LayoutNode。
kotlin
override fun OperationArgContainer.execute(
applier: Applier<*>,
slots: SlotWriter,
rememberManager: RememberManager,
errorContext: OperationErrorContext?,
) {
val groupAnchor = getObject(GroupAnchor)
val insertIndex = getInt(InsertIndex)
applier.up()
val nodeApplier = @Suppress("UNCHECKED_CAST") (applier as Applier<Any?>)
// 从slots数组中取出LayoutNode对象
val nodeToInsert = slots.node(groupAnchor)
// 把节点插入到LayoutNode
nodeApplier.insertBottomUp(insertIndex, nodeToInsert)
}
创建Applier对象
什么时候创建Applier对象?回到doSetContent方法,创建Composition对象的时候创建了UiApplier对象,同时把AndroidComposeView的LayoutNode对象传递给了UiApplier对象。
kotlin
private fun doSetContent(
owner: AndroidComposeView,
parent: CompositionContext,
content: @Composable () -> Unit,
): Composition {
if (isDebugInspectorInfoEnabled && owner.getTag(R.id.inspection_slot_table_set) == null) {
owner.setTag(
R.id.inspection_slot_table_set,
Collections.newSetFromMap(WeakHashMap<CompositionData, Boolean>()),
)
}
// 从标签中取出WrappedComposition对象,如果为空,创建WrappedComposition对象,创建Composition对象,进而创建UiApplier对象
val wrapped =
owner.view.getTag(R.id.wrapped_composition_tag) as? WrappedComposition
?: WrappedComposition(owner, Composition(UiApplier(owner.root), parent)).also {
owner.view.setTag(R.id.wrapped_composition_tag, it)
}
wrapped.setContent(content)
return wrapped
}
五、总结
- 传统函数没有"记忆",Compose使用SlotTable来记住状态和UI结构。
- SlotTable的groups数组用来存储所有组的元数据,是一个扁平化的树结构,编辑groups数组使用了GapBuffer。slots数组用来存储实际数据(State、LayoutNode、RecomposeScope、CompositionLocal、remember等)。
- Compose函数在编译期会被插入startXXXGroup/endXXXGroup模板代码,startXXXGroup会插入Group并通过$key识别Group在代码中的位置。
- LayoutNode树是实际的节点树,用于测量、布局和绘制。SlotTable结构的变化最终要映射到LayoutNode,通过Applier接口来调用到LayoutNode。Applier提供了从上到下、从下到上两种插入方式,安卓使用从下到上的插入方式,因为子节点的变动会影响父节点的重新测量,从下到上插入可以避免影响太多的父节点,提高性能。