Compose原理四之重组

0 阅读9分钟

一、前言

首次组合指的是第一次执行函数,在Compose原理三之SlotTable文章中讲解的就是首次组合,但是一个页面还会有各种各样的刷新操作,声明式UI的刷新靠的是重新执行函数,重组就是重新执行函数。要想知道重组原理,就得先知道快照。在Compose原理一之快照系统文章中通过例子来讲解快照是能够保存历史状态的原因,并没有跟Compose关联起来。本文将会把Compose和快照关联起来,看看Compose是如何借助快照来实现重组。

二、快照

示例代码

依然用下面的示例代码

@Composable
fun Count() {
    var content by remember { mutableStateOf(0) }
    
    Button({
        content++
    }, content = {
        Text(content.toString())
    })
}

Recomposer.composing

首次组合的时候,也就是在调用Count函数之前,Recomposer.composing就创建了快照,并且注册读观察者和写观察者。takeMutableSnapshot创建的是可变快照,支持状态修改和隔离。

// Recomposer.composing
private inline fun <T> composing(
    composition: ControlledComposition,
    modifiedValues: MutableScatterSet<Any>?,
    block: () -> T,
): T {
    // 创建快照
    val snapshot =
        Snapshot.takeMutableSnapshot(
            // 当读取数据的时候,能收到回调
            readObserverOf(composition),
            // 当写数据的时候,能收到回调
            writeObserverOf(composition, modifiedValues),
        )
    try {
        // 进入快照
        return snapshot.enter(block)
    } finally {
        applyAndCheck(snapshot)
    }
}

快照类型详解

GlobalSnapshot

全局唯一,所有线程共享的默认快照。

MutableSnapshot

可变快照,支持状态修改和隔离,Compose创建的就是这个快照。

ReadonlySnapshot

只读快照,只能观察不能修改。

NestedMutableSnapshot

嵌套可变快照,apply到父快照。

TransparentObserverMutableSnapshot

透明观察者快照,只添加观察者不隔离。

snapshot.enter 进入快照

public inline fun <T> enter(block: () -> T): T {
    val previous = makeCurrent()
    try {
        return block()
    } finally {
        restoreCurrent(previous)
    }
}

makeCurrent

默认情况下会创建全局快照,所有线程共享的默认快照。第一次获取threadSnapshot.get()快照为空。接下来将MutableSnapshot设为当前快照。

internal open fun makeCurrent(): Snapshot? {
    // previous为空
    val previous = threadSnapshot.get()
    // 将MutableSnapshot设为当前快照
    threadSnapshot.set(this)
    return previous
}

现在通过Snapshot.current返回就是Compose创建的MutableSnapshot可变快照。

public inline fun <T> enter(block: () -> T): T {
    val previous = makeCurrent()
    try {
        // 在Count函数中获取的快照就是Compose创建的MutableSnapshot可变快照
        return block()
    } finally {
        restoreCurrent(previous)
    }
}

到此我们记住,在Count函数中获取的快照就是Compose创建的MutableSnapshot可变快照。

mutableStateOf(0)

mutableStateOf创建的是SnapshotMutableStateImpl对象,该对象会被保存到slots数组,什么保存到slots数组里面呢?答案就在remember函数。

public fun <T> mutableStateOf(
    value: T,
    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy(),
): MutableState<T> = createSnapshotMutableState(value, policy)

internal expect fun <T> createSnapshotMutableState(
    value: T,
    policy: SnapshotMutationPolicy<T>,
): SnapshotMutableState<T>


internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>,
) : StateObjectImpl(), SnapshotMutableState<T> {
    @Suppress("UNCHECKED_CAST")
    override var value: T
        // 取值的时候执行
        get() = next.readable(this).value
        // 修改值的时候执行
        set(value) =
            next.withCurrent {
                if (!policy.equivalent(it.value, value)) {
                    next.overwritable(this, it) { this.value = value }
                }
            }

    private var next: StateStateRecord<T> =
        currentSnapshot().let { snapshot ->
            StateStateRecord(snapshot.snapshotId, value).also {
                if (snapshot !is GlobalSnapshot) {
                    it.next = StateStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), value)
                }
            }
        }
}

remember

remember函数在首次组合的时候会调用mutableStateOf,返回SnapshotMutableStateImpl对象,将SnapshotMutableStateImpl对象保存到slots数组,返回将SnapshotMutableStateImpl对象。

重组的时候,由于slots数组已经有SnapshotMutableStateImpl对象,直接返回SnapshotMutableStateImpl对象。不管remember函数重复执行多少次,每次获取到的都是同一个SnapshotMutableStateImpl对象。

public inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)

public inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
    @Suppress("UNCHECKED_CAST")
    // 先从slots数组获取
    return rememberedValue().let {
        // 首次组合的时候获取肯定为空
        if (invalid || it === Composer.Empty) {
            // 调用mutableStateOf,返回SnapshotMutableStateImpl对象
            val value = block()
            // 将SnapshotMutableStateImpl对象保存到slots数组
            updateRememberedValue(value)
            value
        } else it // 重组的时候,直接返回SnapshotMutableStateImpl对象
    } as T
}

Text(content.toString())

首次组合,调用Text(content.toString())SnapshotMutableStateImpl中取值,调用到next.readable(this).value,value就是var content by remember { mutableStateOf(0) }中的0了。

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>,
) : StateObjectImpl(), SnapshotMutableState<T> {
    @Suppress("UNCHECKED_CAST")
    override var value: T
        // 取值的时候执行
        get() = next.readable(this).value
        // 修改值的时候执行
        set(value) =
            next.withCurrent {
                if (!policy.equivalent(it.value, value)) {
                    next.overwritable(this, it) { this.value = value }
                }
            }

    private var next: StateStateRecord<T> =
        currentSnapshot().let { snapshot ->
            StateStateRecord(snapshot.snapshotId, value).also {
                if (snapshot !is GlobalSnapshot) {
                    it.next = StateStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), value)
                }
            }
        }
}

readable

关键来了,Snapshot.current获取到的快照是Compose创建的可变快照,由于Compose在创建快照的时候,注册了读观察者,snapshot.readObserver?.invoke(state)就是通知Compose执行。

public fun <T : StateRecord> T.readable(state: StateObject): T {
    // 获取到的快照是Compose创建的可变快照
    val snapshot = Snapshot.current
    // 通知Compose执行
    snapshot.readObserver?.invoke(state)
    return readable(this, snapshot.snapshotId, snapshot.invalid)
        ?: sync {
            // Readable can return null when the global snapshot has been advanced by another thread
            // and state written to the object was overwritten while this thread was paused.
            // Repeating the read is valid here as either this will return the same result as
            // the previous call or will find a valid record. Being in a sync block prevents other
            // threads from writing to this state object until the read completes.
            val syncSnapshot = Snapshot.current
            @Suppress("UNCHECKED_CAST")
            readable(state.firstStateRecord as T, syncSnapshot.snapshotId, syncSnapshot.invalid)
                ?: readError()
        }
}

刚刚提到,Recompose.composing创建快照的时候,注册了读观察者,snapshot.readObserver?.invoke(state)就会调用到readObserverOf,进而调用到Composition.recordReadOf

// Recomposer.kt
private fun readObserverOf(composition: ControlledComposition): (Any) -> Unit {
    return { value -> composition.recordReadOf(value) }
}

// Composition.kt
override fun recordReadOf(value: Any) {
    // Not acquiring lock since this happens during composition with it already held
    if (!areChildrenComposing) {
        // 取出当前的重组作用域对象,当前的重组作用域对象是谁?
        composer.currentRecomposeScope?.let { scope ->
            scope.used = true

            val alreadyRead = scope.recordRead(value)

            observer()?.onReadInScope(scope, value)

            if (!alreadyRead) {
                if (value is StateObjectImpl) {
                    value.recordReadIn(ReaderKind.Composition)
                }

                observations.add(value, scope)

                // Record derived state dependency mapping
                if (value is DerivedState<*>) {
                    val record = value.currentRecord
                    derivedStates.removeScope(value)
                    record.dependencies.forEachKey { dependency ->
                        if (dependency is StateObjectImpl) {
                            dependency.recordReadIn(ReaderKind.Composition)
                        }
                        derivedStates.add(dependency, value)
                    }
                    scope.recordDerivedStateValue(value, record.currentValue)
                }
            }
        }
    }
}

currentRecomposeScope

从失效栈中获取重组作用域对象,栈顶的重组作用域对象是哪个?

internal val currentRecomposeScope: RecomposeScopeImpl?
    get() =
        invalidateStack.let {
            if (childrenComposing == 0 && it.isNotEmpty()) it.peek() else null
        }

首次组合调用Count函数会先调用startRestartGroupstartRestartGroup调用addRecomposeScope。没错,就是在addRecomposeScope里面创建了重组作用域对象,将其放入到失效栈中。从currentRecomposeScope获取的重组作用域对象就是在startRestartGroup创建的重组作用域对象。

override fun startRestartGroup(key: Int): Composer {
    startReplaceGroup(key)
    addRecomposeScope()
    return this
}

private fun addRecomposeScope() {
    if (inserting) {
        // 创建重组作用域对象
        val scope = RecomposeScopeImpl(composition as CompositionImpl)
        // 放入到失效栈中
        invalidateStack.push(scope)
        updateValue(scope)
        enterRecomposeScope(scope)
    }
    

回到recordReadOf方法,参数value是SnapshotMutableState状态对象,获取栈顶的重组作用域对象,将状态对象与重组作用域对象建立映射。

// Composition.kt
override fun recordReadOf(value: Any) {
    if (!areChildrenComposing) {
        // 取出当前的重组作用域对象,就是在`startRestartGroup`创建重组作用域对象
        composer.currentRecomposeScope?.let { scope ->
            scope.used = true
            val alreadyRead = scope.recordRead(value)
            
            if (!alreadyRead) {
                  // 建立状态对象与重组作用域对象的映射
                observations.add(value, scope)
            }
        }
    }
}

到此为止,Text(content.toString())取值的逻辑就结束了。稍稍的总结下: Compose在调用Compose函数前,就创建了可变快照,当取值的时候,状态对象与重组作用域对象存在映射关系。这个隐射后面会用到,大家先记住。

三、重组

首次组合,创建快照,首次获取状态,会将状态对象与重组作用域对象建立映射关系。知道了这个,就可以来看重组的原理。点击按钮,触发状态修改,由于创建了快照,又注册了写观察者,正常应当是能收到回调,事实真的是这样吗?

@Composable
fun Count() {
    var content by remember { mutableStateOf(0) }
    
    Button({
        content++
    }, content = {
        Text(content.toString())
    })
}

// Recomposer.composing
private inline fun <T> composing(
    composition: ControlledComposition,
    modifiedValues: MutableScatterSet<Any>?,
    block: () -> T,
): T {
    // 创建快照
    val snapshot =
        Snapshot.takeMutableSnapshot(
            // 当读取数据的时候,能收到回调
            readObserverOf(composition),
            // 点击按钮修改状态,并没有执行writeObserverOf
            writeObserverOf(composition, modifiedValues),
        )
    try {
        // 进入快照
        return snapshot.enter(block)
    } finally {
        applyAndCheck(snapshot)
    }
}

点击按钮修改状态,并没有执行writeObserverOf,是快照失效了吗?如果快照没失效,那代码又执行到哪了?稍稍的修改下代码,修改状态的时候,打印下当前快照。

@Composable
fun Count() {
    var content by remember { mutableStateOf(0) }
    
    Button({
        content++
        print("当前快照:${Snapshot.current}")
    }, content = {
        Text(content.toString())
    })
}

打印出来的是androidx.compose.runtime.snapshots.GlobalSnapshot@db1e488GlobalSnapshot是全局快照,不是Compose创建的可变快照,自然不会通知Compose执行。

点击修改状态,为什么获取到的是全局快照?点击事件是由系统回调的,不在compose创建的快照内。

全局快照

默认情况下,就已经创建了全局快照,全局快照注册了写入观察者。

// Snapshot.kt
internal class GlobalSnapshot(snapshotId: SnapshotId, invalid: SnapshotIdSet) :
    MutableSnapshot(
        snapshotId,
        invalid,
        readObserver = null,
        writeObserver = {
            // 这里就是全局快照的写入观察者
            // 实际上它会通知通过 Snapshot.registerGlobalWriteObserver 注册的观察者
            state -> sync { globalWriteObservers.fastForEach { it(state) } } 
        }
    )

Compose在哪调用了Snapshot.registerGlobalWriteObserver?在AbstractComposeView.setContent中先调用了GlobalSnapshotManager.ensureStarted(),然后才创建AndroidComposeView

internal fun AbstractComposeView.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit,
): Composition {
    // 确保全局快照执行
    GlobalSnapshotManager.ensureStarted()
    val composeView =
        if (childCount > 0) {
            getChildAt(0) as? AndroidComposeView
        } else {
            removeAllViews()
            null
        }
            ?: AndroidComposeView(context, parent.effectCoroutineContext).also {
                addView(it.view, DefaultLayoutParams)
            }
    return doSetContent(composeView, parent, content)
}

GlobalSnapshotManager.ensureStarted

Snapshot.registerGlobalWriteObserver注册全局的写入观察者,当修改这个的时候,发送信号到通道上,恢复协程,调用sendApplyNotifications

internal object GlobalSnapshotManager {
    private val started = atomic(0)
    private val sent = atomic(0)

    fun ensureStarted() {
        if (started.compareAndSet(0, 1)) {
            val channel = Channel<Unit>(1)
            CoroutineScope(GlobalSnapshotManagerDispatcher).launch {
                channel.consumeEach {
                    sent.compareAndSet(1, 0)
                    Snapshot.sendApplyNotifications()  //调用sendApplyNotifications
                }
            }
            // 注册全局的写入观察者
            Snapshot.registerGlobalWriteObserver {
                if (sent.compareAndSet(0, 1)) {
                    channel.trySend(Unit)  // 发送信号到 channel
                }
            }
        }
    }
}

状态对象的值从0变成1,调用overwritable

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>,
) : StateObjectImpl(), SnapshotMutableState<T> {
    @Suppress("UNCHECKED_CAST")
    override var value: T
        get() = next.readable(this).value
        set(value) =
            next.withCurrent {
                if (!policy.equivalent(it.value, value)) {
                    // 修改值,调用overwritable
                    next.overwritable(this, it) { this.value = value }
                }
            }

    private var next: StateStateRecord<T> =
        currentSnapshot().let { snapshot ->
            StateStateRecord(snapshot.snapshotId, value).also {
                if (snapshot !is GlobalSnapshot) {
                    it.next = StateStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), value)
                }
            }
        }

overwritable

获取全局快照,将状态对象保存到全局快照的modified对象中,通知全局快照执行。

internal inline fun <T : StateRecord, R> T.overwritable(
    state: StateObject,
    candidate: T,
    block: T.() -> R,
): R {
    val snapshot: Snapshot
    return sync {
            // 当前快照为全局快照
            snapshot = Snapshot.current
            // overwritableRecord会调用到recordModified
            this.overwritableRecord(state, snapshot, candidate).block()
        }
        .also { notifyWrite(snapshot, state) }
}

// 将状态对象保存到全局快照的modified对象中
override fun recordModified(state: StateObject) {
    (modified ?: mutableScatterSetOf<StateObject>().also { modified = it }).add(state)
}

internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
    snapshot.writeCount += 1
    // 通知全局快照执行
    snapshot.writeObserver?.invoke(state)
}

通知全局快照执行就是发送信号到通道上,恢复协程,调用sendApplyNotifications

internal object GlobalSnapshotManager {
    private val started = atomic(0)
    private val sent = atomic(0)

    fun ensureStarted() {
        if (started.compareAndSet(0, 1)) {
            val channel = Channel<Unit>(1)
            CoroutineScope(GlobalSnapshotManagerDispatcher).launch {
                channel.consumeEach {
                    sent.compareAndSet(1, 0)
                    Snapshot.sendApplyNotifications()  //调用sendApplyNotifications
                }
            }
            // 注册全局的写入观察者
            Snapshot.registerGlobalWriteObserver {
                if (sent.compareAndSet(0, 1)) {
                    channel.trySend(Unit)  // 发送信号到 channel
                }
            }
        }
    }
}

Snapshot.sendApplyNotifications

从全局快照中获取修改后的状态,遍历观察者,通知Conpose执行,将修改后的状态传给Compose。

public fun sendApplyNotifications() {
    val changes = sync { globalSnapshot.hasPendingChanges() }
    if (changes) advanceGlobalSnapshot()
}

private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
    val globalSnapshot = globalSnapshot

    val modified: MutableScatterSet<StateObject>?
    val result = sync {
        // 刚刚将状态对象保存到了全局快照的modified对象
        modified = globalSnapshot.modified
        if (modified != null) {
            pendingApplyObserverCount.add(1)
        }
        resetGlobalSnapshotLocked(globalSnapshot, block)
    }

    // If the previous global snapshot had any modified states then notify the registered apply
    // observers.
    modified?.let {
        try {
            val observers = applyObservers
            // 遍历观察者,通知Conpose执行,将修改后的状态传给Compose。
            observers.fastForEach { observer -> observer(it.wrapIntoSet(), globalSnapshot) }
        } finally {
            pendingApplyObserverCount.add(-1)
        }
    }

    sync {
        checkAndOverwriteUnusedRecordsLocked()
        modified?.forEach { processForUnusedRecordsLocked(it) }
    }

    return result
}

Recomposer注册applyObserver

AbstractComposeViewonAttachedToWindow创建了Recomposer对象,当Activity/Fragment 的生命周期达到CREATED状态时调用runRecomposeAndApplyChanges启动Recomposer的主循环,开始监听快照变化和帧信号。

// androidx.compose.ui.platform.WindowRecomposer.android.kt

private fun createLifecycleAwareWindowRecomposer(
    rootView: View,
    lifecycle: Lifecycle,
    coroutineContext: CoroutineContext
): Recomposer {
    // 1、创建 Recomposer 对象
    val recomposer = Recomposer(coroutineContext)
    
    // 2、创建协程作用域,绑定到 rootView 的生命周期
    val runnerJob = Job(coroutineContext[Job])
    
    // 3、 在生命周期作用域中启动 Recomposer
    lifecycle.coroutineScope.launch(runnerJob + coroutineContext) {
        try {
            // 等待生命周期进入 CREATED 状态
            lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
                // 4、调用 runRecomposeAndApplyChanges 启动 Recomposer 的主循环
                recomposer.runRecomposeAndApplyChanges()
            }
        } finally {
            // 生命周期结束时关闭 Recomposer
            recomposer.cancel()
        }
    }
    
    return recomposer
}

recompositionRunner

runRecomposeAndApplyChanges调用了recompositionRunner,注册了观察者,最关键的是唤醒了挂起的协程

private suspend fun recompositionRunner(
    block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
) {
  val unregsterApplyObserver =
    // 注册观察者
    Snapshot.registerApplyObserver { changed, _ ->
        // change就是修改值后的状态对象
        synchronized(stateLock) {
            if (_state.value >= State.Idle) {
                val snapshotInvalidations = snapshotInvalidations
                changed.fastForEach {
                    // 遍历修改后的状态
                    if (it is StateObjectImpl && !it.isReadIn(ReaderKind.Composition)) {
                        return@fastForEach  // 跳过未在组合中读取的状态
                    }
                    snapshotInvalidations.add(it)  // 记录需要失效的状态
                }
                deriveStateLocked()  // 派生状态,返回待唤醒的协程
            } else null
        }?.resume(Unit)  // 唤醒挂起的协程
    }
}    

协程唤醒机制

Recomposer的主循环结构

// Recomposer.kt
suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->
   
    
    // 主循环:等待工作 -> 执行工作 -> 重复
    while (shouldKeepRecomposing) {
        awaitWorkAvailable()  // 关键:在这里挂起等待
        // 能执行到这,说明协程被唤醒了
    }
    // 省略部分代码......
}

关键要点:

  1. awaitWorkAvailable() 是协程的挂起点。
  2. 当有工作时(状态变化、帧事件等),协程被唤醒。
  3. 处理完一轮工作后,再次进入等待状态。

awaitWorkAvailable - 协程挂起逻辑

// Recomposer.kt
private suspend fun awaitWorkAvailable() {
    if (!hasSchedulingWork) {
        // 如果当前没有工作,挂起协程
        suspendCancellableCoroutine<Unit> { co ->
            synchronized(stateLock) {
                if (hasSchedulingWork) {
                    // 双重检查:可能在获取锁期间有工作进来了
                    co  // 立即恢复
                } else {
                    // 保存 continuation,等待被唤醒
                    workContinuation = co
                    null  // 不恢复,继续挂起
                }
            }?.resume(Unit)
        }
    }
}

private val hasSchedulingWork: Boolean
    get() = synchronized(stateLock) {
        snapshotInvalidations.isNotEmpty() ||  // 有状态失效
            compositionInvalidations.isNotEmpty() ||  // 有组合失效
            hasBroadcastFrameClockAwaitersLocked  // 有帧时钟等待者
    }

执行流程:

  1. 首先检查 hasSchedulingWork,如果已经有工作,直接返回不挂起。
  2. 调用 suspendCancellableCoroutine 挂起当前协程。
  3. 在锁内再次检查(双重检查模式)。
  4. 如果确实没有工作,将 CancellableContinuation 保存到 workContinuation
  5. 协程在这里挂起,等待被恢复。

deriveStateLocked- 状态计算与唤醒

// Recomposer.kt
private fun deriveStateLocked(): CancellableContinuation<Unit>? {
    // 如果 Recomposer 正在关闭,清理资源
    if (_state.value <= State.ShuttingDown) {
        clearKnownCompositionsLocked()
        snapshotInvalidations = MutableScatterSet()
        compositionInvalidations.clear()
        compositionsAwaitingApply.clear()
        movableContentAwaitingInsert.clear()
        failedCompositions = null
        workContinuation?.cancel()
        workContinuation = null
        errorState = null
        return null
    }
    
    // 计算新状态
    val newState = when {
        errorState != null -> State.Inactive
        runnerJob == null -> {
            snapshotInvalidations = MutableScatterSet()
            compositionInvalidations.clear()
            if (hasBroadcastFrameClockAwaitersLocked) State.InactivePendingWork
            else State.Inactive
        }
        // 关键判断:是否有待处理的工作
        compositionInvalidations.isNotEmpty() ||
            snapshotInvalidations.isNotEmpty() ||
            compositionsAwaitingApply.isNotEmpty() ||
            movableContentAwaitingInsert.isNotEmpty() ||
            concurrentCompositionsOutstanding > 0 ||
            hasBroadcastFrameClockAwaitersLocked ||
            movableContentRemoved.isNotEmpty() -> State.PendingWork
        else -> State.Idle
    }
    
    _state.value = newState
    
    // 如果状态变为 PendingWork,返回 continuation 以便唤醒
    return if (newState == State.PendingWork) {
        workContinuation.also { workContinuation = null }
    } else null
}

核心逻辑:

  1. 如果计算出的状态是 PendingWork(有工作待处理)。
  2. 取出之前保存的 workContinuation 并返回。
  3. 调用者会调用 continuation.resume(Unit) 唤醒挂起的协程。

runRecomposeAndApplyChanges

协程被唤醒后,当下一帧来临的时候,调用performRecompose

// Recomposer.kt
public suspend fun runRecomposeAndApplyChanges(): Unit =
    recompositionRunner { parentFrameClock ->
        while (shouldKeepRecomposing) {
            awaitWorkAvailable()
            // 执行到这,说明协程被唤醒
            // 监听下一帧
            parentFrameClock.withFrameNanos { frameTime ->
                trace("Recomposer:recompose") {
                    // 下一帧来了
                    while (toRecompose.isNotEmpty() || toInsert.isNotEmpty()) {
                        try {
                            toRecompose.fastForEach { composition ->
                                // 调用performRecompose
                                performRecompose(composition, modifiedValues)?.let {
                                    toApply += it
                                }
                                alreadyComposed.add(composition)
                            }
                        } catch (e: Throwable) {
                            processCompositionError(e, recoverable = true)
                            clearRecompositionState()
                            return@withFrameNanos
                        } finally {
                            toRecompose.clear()
                        }
                    }
                }
            }
        }
    }

performRecompose

调用composingcomposing会创建快照,每次重组的时候,其实都会创建快照。

// Recomposer.kt
private fun performRecompose(
    composition: ControlledComposition,
    modifiedValues: MutableScatterSet<Any>?,
): ControlledComposition? {
    if (
        composition.isComposing ||
            composition.isDisposed ||
            compositionsRemoved?.contains(composition) == true
    )
        return null

    return if (
        // 
        composing(composition, modifiedValues) {
            if (modifiedValues?.isNotEmpty() == true) {
                // Record write performed by a previous composition as if they happened during
                // composition.
                composition.prepareCompose {
                    modifiedValues.forEach { composition.recordWriteOf(it) }
                }
            }
            // 重组
            composition.recompose()
        }
    )
        composition
    else null
}

private inline fun <T> composing(
    composition: ControlledComposition,
    modifiedValues: MutableScatterSet<Any>?,
    block: () -> T,
): T {
    val snapshot =
        Snapshot.takeMutableSnapshot(
            readObserverOf(composition),
            writeObserverOf(composition, modifiedValues),
        )
    try {
        return snapshot.enter(block)
    } finally {
        applyAndCheck(snapshot)
    }
}

recordWriteOf

recordWriteOf调用invalidateScopeOfLocked

// Composition.kt
override fun recordWriteOf(value: Any) =
    synchronized(lock) {
        invalidateScopeOfLocked(value)
        derivedStates.forEachScopeOf(value) { invalidateScopeOfLocked(it) }
    }

invalidateScopeOfLocked

我们看到了关键的observations,在首次组合获取状态的时候,Compose将状态对象与重组作用域对象建立映射。这个刚刚讲过,还特意让大家记住。遍历observations,取出重组作用域对象,调用invalidateForResultinvalidateForResult会调用到invalidateChecked

// Composition.kt
private fun invalidateScopeOfLocked(value: Any) {
    // 取出重组作用域对象
    observations.forEachScopeOf(value) { scope ->
        if (scope.invalidateForResult(value) == InvalidationResult.IMMINENT) {
            observationsProcessed.add(value, scope)
        }
    }
}

invalidateChecked

将重组作用域对象保存到invalidations,后续会从invalidations取出重组作用域对象。

// Composition.kt
private fun invalidateChecked(
    scope: RecomposeScopeImpl,
    anchor: Anchor,
    instance: Any?,
): InvalidationResult {
    val delegate =
        synchronized(lock) {
            val delegate =
                invalidationDelegate?.let { changeDelegate ->
                    if (slotTable.groupContainsAnchor(invalidationDelegateGroup, anchor)) {
                        changeDelegate
                    } else null
                }
            if (delegate == null) {
                if (tryImminentInvalidation(scope, instance)) {
                    return InvalidationResult.IMMINENT
                }
                if (instance == null) {
                    // 将重组作用域对象保存到invalidations
                    invalidations.set(scope, ScopeInvalidated)
                } else if (instance !is DerivedState<*>) {
                    // 将重组作用域对象保存到invalidations
                    invalidations.set(scope, ScopeInvalidated)
                } else {
                    if (!invalidations.anyScopeOf(scope) { it === ScopeInvalidated }) {
                        invalidations.add(scope, instance)
                    }
                }
            }
            delegate
        }
    parent.invalidate(this)
    return if (isComposing) InvalidationResult.DEFERRED else InvalidationResult.SCHEDULED
}

recompose

将重组作用域对象保存到invalidations后,调用recomposerecompose调用doCompose

// Composer.kt
internal fun recompose(
    invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
    shouldPause: ShouldPauseCallback?,
): Boolean {
    if (invalidationsRequested.size > 0 || invalidations.isNotEmpty() || forciblyRecompose) {
        shouldPauseCallback = shouldPause
        try {
            doCompose(invalidationsRequested, null)
        } finally {
            shouldPauseCallback = null
        }
        return changes.isNotEmpty()
    }
    return false
}

doCompose

doCompose的第一个参数就是invalidations,第二个参数为空。首次组合的时候,也会调用doCompose,首次组合,doCompose的第一个参数为空,第二个参数非空。

// Composer.kt
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
        // 将invalidationsRequested转成invalidations
        updateComposerInvalidations(invalidationsRequested)
        nodeIndex = 0
        var complete = false
        isComposing = true
        observer?.onBeginComposition(composition)
        try {
            startRoot()
            observeDerivedStateRecalculations(derivedStateObserver) {
                if (content != null) {
                    // 首次组合的时候执行
                    startGroup(invocationKey, invocation)
                    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()
        }
    }
}

recomposeToGroupEnd

skipCurrentGroup调用recomposeToGroupEnd,取出重组作用域对象,调用compose。

// Composer.kt
private fun recomposeToGroupEnd() {
    val wasComposing = isComposing
    isComposing = true
    var recomposed = false

    val parent = reader.parent
    val end = parent + reader.groupSize(parent)
    val recomposeIndex = nodeIndex
    val recomposeCompositeKey = this@ComposerImpl.compositeKeyHashCode
    val oldGroupNodeCount = groupNodeCount
    val oldRGroupIndex = rGroupIndex
    var oldGroup = parent

    var firstInRange = invalidations.firstInRange(reader.currentGroup, end)
    while (firstInRange != null) {
        val location = firstInRange.location
        // 取出重组作用域对象
        val scope = firstInRange.scope

        invalidations.removeLocation(location)

        if (firstInRange.isInvalid()) {
            // 失效
            recomposed = true

            reader.reposition(location)
            val newGroup = reader.currentGroup
            // Record the changes to the applier location
            recordUpsAndDowns(oldGroup, newGroup, parent)
            oldGroup = newGroup
            nodeIndex = nodeIndexOf(location, newGroup, parent, recomposeIndex)

            // 调用
            scope.compose(this)

            
            providerCache = null

            // Restore the parent of the reader to the previous parent
            reader.restoreParent(parent)
        } else {
            
            invalidateStack.push(scope)
            val observer = observerHolder.current()
            if (observer != null) {
                try {
                    observer.onScopeEnter(scope)
                    scope.rereadTrackedInstances()
                } finally {
                    observer.onScopeExit(scope)
                }
            } else {
                scope.rereadTrackedInstances()
            }
            invalidateStack.pop()
        }

      
        firstInRange = invalidations.firstInRange(reader.currentGroup, end)
    }

    isComposing = wasComposing
}

compose

block是什么?它就是我们需要重新执行的函数,block?.invoke(composer, 1)就是在重新执行函数。block什么时候被赋值?Compose原理三之SlotTable文章中我们就介绍过,首次组合的时候,先调用startRestartGroup,创建重组作用域对象,后调用了endRestartGroupendRestartGroup返回了重组作用域对象,调用重组作用域对象的updateScope,将需要重新执行的函数赋值给了block

// RecomposerScopeImpl.kt
fun compose(composer: Composer) {
    // 执行重组,调用函数
    block?.invoke(composer, 1) ?: error("Invalid restart scope")
}

override fun updateScope(block: (Composer, Int) -> Unit) {
    this.block = block
}

历经千辛万苦,我们终于看到了Compose函数是如何重新执行的,最关键的重组作用域对象在不同的对象中来回穿梭,真是复杂。

重新执行函数

重组就是重新下面的代码

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;
}

startRestartGroup

//Composer.kt
override fun startRestartGroup(key: Int): Composer {
    startReplaceGroup(key)
    addRecomposeScope()
    return this
}

startReplaceGroup

首次组合,key保存到了groups数组,重组的时候,从groups数组取出的key与传入的key相等,进入更新组的信息。

//Composer.kt
override fun startReplaceGroup(key: Int) {
    // 重组,inserting为false
    if (inserting) {
        reader.beginEmpty()
        writer.startGroup(key, Composer.Empty)
        enterGroup(false, null)
        return
    }
    // 首次组合,key保存到了groups数组
    val slotKey = reader.groupKey
    // 重组,key相等
    if (slotKey == key && !reader.hasObjectKey) {
        // 读取组的信息
        reader.startGroup()
        enterGroup(false, null)
        return
    }
}

startGroup

读取组的信息

// SlotTable.kt
fun startGroup() {
    if (emptyCount <= 0) {
        val parent = parent
        val currentGroup = currentGroup
        requirePrecondition(groups.parentAnchor(currentGroup) == parent) {
            "Invalid slot table detected"
        }
        sourceInformationMap?.get(anchor(parent))?.reportGroup(table, currentGroup)
        val currentSlotStack = currentSlotStack
        val currentSlot = currentSlot
        val currentEndSlot = currentSlotEnd
        if (currentSlot == 0 && currentEndSlot == 0) {
            currentSlotStack.push(-1)
        } else {
            currentSlotStack.push(currentSlot)
        }
        this.parent = currentGroup
        currentEnd = currentGroup + groups.groupSize(currentGroup)
        this.currentGroup = currentGroup + 1
        this.currentSlot = groups.slotAnchor(currentGroup)
        this.currentSlotEnd =
            if (currentGroup >= groupsSize - 1) slotsSize
            else groups.dataAnchor(currentGroup + 1)
    }
}

addRecomposeScope

重组的时候,inserting为false,从slots数组取出重组作用域对象,由于后续还需要执行endReplaceGroup,所以还是需要将重组作用域对象添加到失效栈。

// Composer.kt
private fun addRecomposeScope() {
    // inserting为false
    if (inserting) {
        val scope = RecomposeScopeImpl(composition as CompositionImpl)
        invalidateStack.push(scope)
        updateValue(scope)
        enterRecomposeScope(scope)
    } else {
        val invalidation = invalidations.removeLocation(reader.parent)
        // 从slots数组取出重组作用域对象
        val slot = reader.next()
        val scope =
            if (slot == Composer.Empty) {
                // This code is executed when a previously deactivate region is becomes active
                // again. See Composer.deactivateToEndGroup()
                val newScope = RecomposeScopeImpl(composition as CompositionImpl)
                updateValue(newScope)
                newScope
            } else slot as RecomposeScopeImpl
        scope.requiresRecompose =
            invalidation != null ||
                scope.forcedRecompose.also { forced ->
                    if (forced) scope.forcedRecompose = false
                }
        // 添加到失效栈        
        invalidateStack.push(scope)
        enterRecomposeScope(scope)

        if (scope.paused) {
            scope.paused = false
            scope.resuming = true
            changeListWriter.startResumingScope(scope)
            if (!reusing && scope.reusing) {
                reusing = true
                scope.resetReusing = true
            }
        }
    }
}

remember

首次组合,创建状态对象,保存到slots数组,重组的时候从slots数组中取出状态对象。有了slots数组,组合和重组的状态对象就是同一个。

public inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)
   

public inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
    @Suppress("UNCHECKED_CAST")
    // 从slots数组中取出状态对象,重组的时候,状态对象不为空,直接返回
    return rememberedValue().let {
        if (invalid || it === Composer.Empty) {
            val value = block()
            updateRememberedValue(value)
            value
        } else it
    } as T
}

endRestartGroup

从失效栈中取出重组作用域对象,返回重组作用域对象。

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保存需要重新执行的函数,下次重组,重复流程。

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;
}

总结

不管是首次组合还是重组,都会创建快照。首次组合,创建重组作用域对象,获取状态时,Compose会将状态对象与重组作用域对象建立映射。

点击按钮修改状态,点击事件是由系统回调的,不在Compose创建的快照内,通过全局快照回调,Compose注册了回调。唤醒协程,当下一帧来临时,取出重组作用域对象,重新执行函数。

重组的时候,不是重新创建对象,而是从slot数组中取出重组作用域对象和状态对象,函数就能够正常执行。

问个问题

@Composable
fun Count() {
    var content by remember { mutableStateOf(0) }

    Button({
        content++
    }, content = {
        TextWrapper {
            Text(content.toString())
        }
    })
}

@Composable
fun TextWrapper(content: @Composable () -> Unit) {
    content()
}

如上代码,Count函数和 TextWrapper函数都被编译器插入了startRestartGroup,都会创建重组作用域对象。但点击按钮后,只有TextWrapper函数会重组,而Count函数不会。这是如何实现的?答案就散落在本文中,如果你读懂了本文,那问题应当不难。

欢迎大家在评论区讨论。