Compose原理三之SlotTable

一、前言

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需要:

  1. 记住状态 - remember的值在重组间保持。
  2. 记住 UI 结构 - 知道哪些组件存在,它们的顺序。
  3. 比较差异 - 重组时知道什么变了,什么没变。
  4. 高效更新 - 只更新真正需要更新的部分。

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会调用ApplierinsertTopDown方法,PostInsertNodeFixup会调用ApplierinsertBottomUp方法。关于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")
}

应用变更

ButtonText等节点都保存到了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会调用到InsertNodeFixupsexecute。在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)
}

到此,ButtonText组件也被放到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?执行完InsertNodeFixupsexecute后,还会执行PostInsertNodeFixupsexecute,从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对象,同时把AndroidComposeViewLayoutNode对象传递给了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提供了从上到下、从下到上两种插入方式,安卓使用从下到上的插入方式,因为子节点的变动会影响父节点的重新测量,从下到上插入可以避免影响太多的父节点,提高性能。
相关推荐
狮子座明仔2 小时前
M-ASK 论文解读:超越单体架构的多智能体搜索与知识优化框架
人工智能·深度学习·语言模型·自然语言处理·架构
Lonely丶墨轩2 小时前
文档上传功能技术架构与实现指南
架构
APItesterCris3 小时前
商品详情 API 的签名验证与安全接入技术要点
大数据·数据库·安全·架构
智能化咨询3 小时前
(122页PPT)数字化架构演进和治理(附下载方式)
微服务·云原生·架构
萧曵 丶3 小时前
微服务集成「分布式事务」
分布式·微服务·架构
极客Kimi3 小时前
从Java架构到AI架构:机器学习、深度学习与LLM的技术融合之路
java·人工智能·架构
西红市杰出青年4 小时前
crawl4ai------AsyncPlaywrightCrawlerStrategy使用教程
开发语言·python·架构·正则表达式·pandas
Qiuner4 小时前
一文读懂 Lambda
java·spring boot·后端·架构
东方佑4 小时前
思维自指:LLM推理架构的维度突破与意识雏形
人工智能·架构