ReComposition

类设计

GeneratedClass

JetnewsApp传递ComposableLambdaImpl:AppContent{}给JetnewsTheme

@Composable
fun JetnewsApp(
    appContainer: AppContainer,
    navigationViewModel: NavigationViewModel
) {
    //调用方法JetnewsTheme,并将AppContent闭包作为composableLambda传递给JetnewsTheme
    JetnewsTheme {
        //每个调用位置都有一个特殊的key,这里是-819895379
        AppContent(
            navigationViewModel = navigationViewModel,
            interestsRepository = appContainer.interestsRepository,
            postsRepository = appContainer.postsRepository
        )
    }
}

@Composable
fun JetnewsTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {......}
public final class JetnewsAppKt {
    public static final void JetnewsApp(AppContainer appContainer, NavigationViewModel navigationViewModel, Composer $composer, int $changed) {
        int i;......
        Composer $composer2 = $composer.startRestartGroup(1936574869, "C(JetnewsApp)35@1302L232:JetnewsApp.kt#x6nqmx");
        if (($changed & 14) == 0) {
            i = ($composer2.changed(appContainer) ? 4 : 2) | $changed;
        } else {
            i = $changed;
        }
        if (($changed & 112) == 0) {
            i |= $composer2.changed(navigationViewModel) ? 32 : 16;
        }
        if (((i & 91) ^ 18) != 0 || !$composer2.getSkipping()) {
            ThemeKt.JetnewsTheme(false, ComposableLambdaKt.composableLambda($composer2, -819895379, true, "C36@1325L203:JetnewsApp.kt#x6nqmx", new JetnewsAppKt$JetnewsApp$1(appContainer, navigationViewModel, i)), $composer2, 48, 1);
        } else {
            $composer2.skipToGroupEnd();
        }
        ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
        if (endRestartGroup != null) {
            endRestartGroup.updateScope(new JetnewsAppKt$JetnewsApp$2(appContainer, navigationViewModel, $changed));
        }
    }
final class JetnewsAppKt$JetnewsApp$1 extends Lambda implements Function2<Composer, Integer, Unit> {
    public final void invoke(Composer $composer, int $changed) {
        if ((($changed & 11) ^ 2) != 0 || !$composer.getSkipping()) {
            InterestsRepository interestsRepository = this.$appContainer.getInterestsRepository();
            JetnewsAppKt.access$AppContent(this.$navigationViewModel, this.$appContainer.getPostsRepository(), interestsRepository, $composer, (this.$$dirty >> 3) & 14);
            return;
        }
        $composer.skipToGroupEnd();
    }
}

public final class JetnewsAppKt$JetnewsApp$2 extends Lambda implements Function2<Composer, Integer, Unit> {
        public final void invoke(Composer composer, int i) {
        JetnewsAppKt.JetnewsApp(this.$appContainer, this.$navigationViewModel, composer, this.$$changed | 1);
    }
}

composableLambda执行闭包

content: @Composable () -> Unit
content()//执行ComposableLambdaImpl
//ComposableLambdaImpl
override operator fun invoke(c: Composer, changed: Int): Any? {
    val c = c.startRestartGroup(key, sourceInformation)
    trackRead(c)
    val dirty = changed or if (c.changed(this)) differentBits(0) else sameBits(0)
    val result = (_block as (c: Composer, changed: Int) -> Any?)(c, dirty)//main
    c.endRestartGroup()?.updateScope(this as (Composer, Int) -> Unit)
    return result
}

闭包执行流程进入了对应的方法调用

本例是AppContent方法

AppContent(
    navigationViewModel = navigationViewModel,
    interestsRepository = appContainer.interestsRepository,
    postsRepository = appContainer.postsRepository
)
@Composable
private fun AppContent(
    navigationViewModel: NavigationViewModel,
    postsRepository: PostsRepository,
    interestsRepository: InterestsRepository
) {
    Crossfade(navigationViewModel.currentScreen) { screen ->
        //key = -819896238                                         
        Surface(color = MaterialTheme.colors.background) {
            when (screen) {
                is Screen.Home -> HomeScreen(
                    navigateTo = navigationViewModel::navigateTo,
                    postsRepository = postsRepository
                )
                is Screen.Interests -> InterestsScreen(
                    navigateTo = navigationViewModel::navigateTo,
                    interestsRepository = interestsRepository
                )
                is Screen.Article -> ArticleScreen(
                    postId = screen.postId,
                    postsRepository = postsRepository,
                    onBack = { navigationViewModel.onBack() }
                )
            }
        }
    }
    //$composer是 composerImpl实例,
    //$changed运行时为4,也就是0x100,也就是说仅有第一个参数navigationViewModel是可变参数,需要进行数据变化跟踪
    public static final void AppContent(NavigationViewModel navigationViewModel, PostsRepository postsRepository, InterestsRepository interestsRepository, Composer $composer, int $changed) {
        int i;
        //AppContent的调用处和这里的AppContent方法定义处,都有自己的groupId,并都会进行start和end调用
        Composer $composer2 = $composer.startRestartGroup(-194496041, "C(AppContent)P(1,2)50@1711L790:JetnewsApp.kt#x6nqmx");
        if (($changed & 14) == 0) {
            i = ($composer2.changed(navigationViewModel) ? 4 : 2) | $changed;
        } else {
            i = $changed;
        }
        if (($changed & 112) == 0) {
            i |= $composer2.changed(postsRepository) ? 32 : 16;
        }
        if (($changed & 896) == 0) {
            i |= $composer2.changed(interestsRepository) ? 256 : 128;
        }
        if (((i & 731) ^ 146) != 0 || !$composer2.getSkipping()) {
            CrossfadeKt.Crossfade(navigationViewModel.getCurrentScreen(), null, null, ComposableLambdaKt.composableLambda($composer2, -819896238, true, "C51@1806L6,51@1776L719:JetnewsApp.kt#x6nqmx", new JetnewsAppKt$AppContent$1(navigationViewModel, postsRepository, i, interestsRepository)), $composer2, 3584, 6);
        } else {
            $composer2.skipToGroupEnd();
        }
        ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
        if (endRestartGroup != null) {
            endRestartGroup.updateScope(new JetnewsAppKt$AppContent$2(navigationViewModel, postsRepository, interestsRepository, $changed));
        }
    }

@Composable方法体

startRestartGroup

/**
 * A Compose compiler plugin API. DO NOT call directly.
 *
 * Called to record a group for a [Composable] function and starts a group that can be
 * recomposed on demand based on the lambda passed to
 * [updateScope][ScopeUpdateScope.updateScope] when [endRestartGroup] is called
 *
 * @param key An compiler generated key based on the source location of the call.
 * @param sourceInformation An optional string value to that provides the compose tools enough
 * information to calculate the source location calls made in the group.
 * @return the instance of the composer to use for the rest of the function.
 */

    @ComposeCompilerApi
    override fun startRestartGroup(key: Int, sourceInformation: String?): Composer {
        start(key, null, false, sourceInformation)
        addRecomposeScope()
        return this
    }

start

private fun start(key: Int, objectKey: Any?, isNode: Boolean, data: Any?) {
// Check for the insert fast path. If we are already inserting (creating nodes) then
// there is no need to track insert, deletes and moves with a pending changes object.
if (inserting) {
    reader.beginEmpty()
    val startIndex = writer.currentGroup
    when {
        isNode -> writer.startNode(Composer.Empty)
        data != null -> writer.startData(key, objectKey ?: Composer.Empty, data)
        else -> writer.startGroup(key, objectKey ?: Composer.Empty)
    }
            enterGroup(isNode, null)
            return
}
private fun enterGroup(isNode: Boolean, newPending: Pending?) {
    // When entering a group all the information about the parent should be saved, to be
    // restored when end() is called, and all the tracking counters set to initial state for the
    // group.
    pendingStack.push(pending)
    this.pending = newPending
    this.nodeIndexStack.push(nodeIndex)
    if (isNode) nodeIndex = 0
    this.groupNodeCountStack.push(groupNodeCount)
    groupNodeCount = 0
}

addRecomposeScope

private fun addRecomposeScope() {
    if (inserting) {
        val scope = RecomposeScopeImpl(this)
        invalidateStack.push(scope)
        updateValue(scope)
    } else {
        val invalidation = invalidations.removeLocation(reader.parent)
        val scope = reader.next() as RecomposeScopeImpl
        scope.requiresRecompose = invalidation != null
        invalidateStack.push(scope)
    }
}

ComposerImpl.changed

/**
 * Determine if the current slot table value is equal to the given value, if true, the value
 * is scheduled to be skipped during [applyChanges] and [changes] return false; otherwise
 * [applyChanges] will update the slot table to [value]. In either case the composer's slot
 * table is advanced.
 *
 * @param value the value to be compared.
 */
@ComposeCompilerApi
override fun changed(value: Any?): Boolean {
    return if (nextSlot() != value) {
        updateValue(value)
        true
    } else {
        false
    }
}

getSkipping

/**
 * A Compose compiler plugin API. DO NOT call directly.
 *
 * Reflects whether the [Composable] function can skip. Even if a [Composable] function is
 * called with the same parameters it might still need to run because, for example, a new
 * value was provided for a [CompositionLocal] created by [staticCompositionLocalOf].
 */
@ComposeCompilerApi
val skipping: Boolean

深度优先Stack结构

graph TB
subgraph 深度优先Stack
methodA("methodA-groupA")-->methodB("methodB-groupB")
methodA-->methodC("methodC-groupC")
methodB-->methodD("methodD-groupD")
methodB-->methodE("methodE-groupE")
methodB-->methodF("methodF-groupF")
end

subgraph 对应关系
composable--对应---lambdaSubClass--对应---method--对应---group--对应---flutter的widget
end

endRestartGroup

/**
 * End a restart group. If the recompose scope was marked used during composition then a
 * [ScopeUpdateScope] is returned that allows attaching a lambda that will produce the same
 * composition as was produced by this group (including calling [startRestartGroup] and
 * [endRestartGroup]).
 */
    @ComposeCompilerApi
    override fun endRestartGroup(): ScopeUpdateScope? {
        // This allows for the invalidate stack to be out of sync since this might be called during exception stack
        // unwinding that might have not called the doneJoin/endRestartGroup in the wrong order.
        val scope = if (invalidateStack.isNotEmpty()) invalidateStack.pop()
        else null
        scope?.requiresRecompose = false
        val result = if (scope != null && (scope.used || collectParameterInformation)) {
            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

/** RecomposeScopeImpl.kt
 * Update [block]. The scope is returned by [Composer.endRestartGroup] when [used] is true
 * and implements [ScopeUpdateScope].
 */
override fun updateScope(block: (Composer, Int) -> Unit) { this.block = block }
//Composer
/**
 * A Compose internal property. DO NOT call directly. Use [currentRecomposeScope] instead.
 *
 * The invalidation current invalidation scope. An new invalidation scope is created whenever
 * [startRestartGroup] is called. when this scope's [RecomposeScope.invalidate] is called
 * then lambda supplied to [endRestartGroup]'s [ScopeUpdateScope] will be scheduled to be
 * run.
 */
@InternalComposeApi
val recomposeScope: RecomposeScope?

composableLambda

ComposableLambda.kt

@Suppress("unused")
@ComposeCompilerApi
fun composableLambda(
    composer: Composer,
    key: Int,
    tracked: Boolean,
    sourceInformation: String?,
    block: Any
): ComposableLambda {
    composer.startReplaceableGroup(key)
    val slot = composer.rememberedValue()
    val result = if (slot === Composer.Empty) {
        val value = ComposableLambdaImpl(key, tracked, sourceInformation)
        composer.updateRememberedValue(value)
        value
    } else {
        slot as ComposableLambdaImpl
    }
    result.update(block)
    composer.endReplaceableGroup()
    return result
}

ReCompose

图解

runRecomposeAndApplyChanges过程

sequenceDiagram

RecomposeScopeImpl->>RecomposeScopeImpl: invalidate

AndroidUiFrameClock->>AndroidUiFrameClock:suspend withFrameNanos
Note right of AndroidUiFrameClock: suspendCancellableCoroutine

AndroidUiFrameClock->>AndroidUiDispatcher:postFrameCallback(callback)

Note right of AndroidUiDispatcher: 通过Choreographer注册Vsync

AndroidUiDispatcher->>AndroidUiDispatcher: dispatchCallback.doFrame

AndroidUiDispatcher->>AndroidUiFrameClock: toRun[i].doFrame(frameTimeNanos)

Note right of AndroidUiFrameClock: 恢复suspendCancellableCoroutine挂起的协程

AndroidUiFrameClock->>AndroidUiFrameClock: performRecompose

AndroidUiFrameClock->>RecomposeScopeImpl:compose

RecomposeScopeImpl->>RecomposeScopeImpl: block?.invoke(composer, 1)
Note right of RecomposeScopeImpl: 该block为上次composition时通过updateScope设置进来的block也就是lambdaSubClass

AndroidUiFrameClock->>ComposerImpl: applyChanges
ComposerImpl->>ComposerImpl:slotTable.write

runRecomposeAndApplyChanges…recompositionRunner

suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->
    ......
private val knownCompositions = mutableListOf<ControlledComposition>()
private suspend fun recompositionRunner(
    block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
) {
            try {
                // Invalidate all registered composers when we start since we weren't observing
                // snapshot changes on their behalf. Assume anything could have changed.
                synchronized(stateLock) {
                    knownCompositions.fastForEach { it.invalidateAll() }
                    // Don't need to deriveStateLocked here; invalidate will do it if needed.
                }

                coroutineScope {
                    block(parentFrameClock)
                }

CompositonImpl.invalidateAll

override fun invalidateAll() {
    synchronized(lock) {
        composer.invalidateAll()
    }
}

ComposerImpl.invalidateAll

/**
 * Invalidate all known RecomposeScopes. Used by [Recomposer] to bring known composers
 * back into a known good state after a period of time when snapshot changes were not
 * being observed.
 */
internal fun invalidateAll() {
    slotTable.slots.forEach { (it as? RecomposeScopeImpl)?.invalidate() }
}

RecomposeScopeImpl.invalidate

override fun invalidate() {
    invalidateForResult()
}
fun invalidateForResult(): InvalidationResult =
    (composer as? ComposerImpl)?.invalidate(this) ?: InvalidationResult.IGNORED

ComposerImpl.invalidate

internal fun invalidate(scope: RecomposeScopeImpl): InvalidationResult {
    if (scope.defaultsInScope) {
        scope.defaultsInvalid = true
    }
    val anchor = scope.anchor
    ......
    parentContext.invalidate(composition)
    return if (isComposing) InvalidationResult.DEFERRED else InvalidationResult.SCHEDULED
}

CompositionContextImpl.invalidate

private inner class CompositionContextImpl(
        override val compoundHashKey: Int,
        override val collectingParameterInformation: Boolean
    ) : CompositionContext() {
        
override fun invalidate(composition: ControlledComposition) {
    parentContext.invalidate(this@ComposerImpl.composition)
    parentContext.invalidate(composition)
}

ReComposer.invalidate

internal override fun invalidate(composition: ControlledComposition) {
    synchronized(stateLock) {
        if (composition !in compositionInvalidations) {
            compositionInvalidations += composition
            deriveStateLocked()
        } else null
    }?.resume(Unit)
}

Choreographer

private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;

    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            ((FrameCallback)action).doFrame(frameTimeNanos);//main
        } else {
            ((Runnable)action).run();
        }
    }
}

AndroidUiDispatcher.dispatchCallback.doFrame

class AndroidUiDispatcher private constructor(
    val choreographer: Choreographer,
    private val handler: android.os.Handler
) : CoroutineDispatcher() {
    private var toRunOnFrame = mutableListOf<Choreographer.FrameCallback>()

    private val dispatchCallback = object : Choreographer.FrameCallback, Runnable {
          override fun doFrame(frameTimeNanos: Long) {
            handler.removeCallbacks(this)
            performTrampolineDispatch()
            performFrameDispatch(frameTimeNanos)//main
        }
    }
}
private fun performFrameDispatch(frameTimeNanos: Long) {
    val toRun = synchronized(lock) {
        if (!scheduledFrameDispatch) return
        scheduledFrameDispatch = false
        val result = toRunOnFrame
        toRunOnFrame = spareToRunOnFrame
        spareToRunOnFrame = result
        result
    }
    for (i in 0 until toRun.size) {
        // This callback will not and must not throw, see AndroidUiFrameClock
        toRun[i].doFrame(frameTimeNanos)//main
    }
    toRun.clear()
}

AndroidUiFrameClock…resumeWith

class AndroidUiFrameClock(
    val choreographer: Choreographer
) : androidx.compose.runtime.MonotonicFrameClock {
    
    override suspend fun <R> withFrameNanos(
        onFrame: (Long) -> R
    ): R {
        val uiDispatcher = coroutineContext[ContinuationInterceptor] as? AndroidUiDispatcher
        return suspendCancellableCoroutine { co ->
            // Important: this callback won't throw, and AndroidUiDispatcher counts on it.
            val callback = Choreographer.FrameCallback { frameTimeNanos ->
                co.resumeWith(runCatching { onFrame(frameTimeNanos) })
            }

            // If we're on an AndroidUiDispatcher then we post callback to happen *after*
            // the greedy trampoline dispatch is complete.
            // This means that onFrame will run on the current choreographer frame if one is
            // already in progress, but withFrameNanos will *not* resume until the frame
            // is complete. This prevents multiple calls to withFrameNanos immediately dispatching
            // on the same frame.

            if (uiDispatcher != null && uiDispatcher.choreographer == choreographer) {
                uiDispatcher.postFrameCallback(callback)
                co.invokeOnCancellation { uiDispatcher.removeFrameCallback(callback) }
            } else {
                choreographer.postFrameCallback(callback)
                co.invokeOnCancellation { choreographer.removeFrameCallback(callback) }
            }
        }
    }
}

runRecomposeAndApplyChanges…performRecompose

suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->
    val toRecompose = mutableListOf<ControlledComposition>()
    val toApply = mutableListOf<ControlledComposition>()
    while (shouldKeepRecomposing) {
        awaitWorkAvailable()

        // Don't await a new frame if we don't have frame-scoped work
        if (
            synchronized(stateLock) {
                if (!hasFrameWorkLocked) {
                    recordComposerModificationsLocked()
                    !hasFrameWorkLocked
                } else false
            }
        ) continue
        
            parentFrameClock.withFrameNanos { frameTime ->
                trace("recomposeFrame") {
                    ......
                    // Drain any composer invalidations from snapshot changes and record
                    // composers to work on
                    synchronized(stateLock) {
                        recordComposerModificationsLocked()

                        compositionInvalidations.fastForEach { toRecompose += it }
                        compositionInvalidations.clear()
                    }
                    // Perform recomposition for any invalidated composers
                    val modifiedValues = IdentityArraySet<Any>()
                    try {
                        toRecompose.fastForEach { composer ->
                             //main
                            performRecompose(composer, modifiedValues)?.let {
                                toApply += it
                            }
                        }
                        if (toApply.isNotEmpty()) changeCount++
                    } finally {
                        toRecompose.clear()
                    }
                    
                    // Perform apply changes
                    try {
                        toApply.fastForEach { composition ->
                            composition.applyChanges()
                        }
                    } finally {
                        toApply.clear()
                    }

recomposer.performRecompose

private fun performRecompose(
    composition: ControlledComposition,
    modifiedValues: IdentityArraySet<Any>?
): ControlledComposition? {
    if (composition.isComposing || composition.isDisposed) return null
    return if (
        composing(composition, modifiedValues) {
            modifiedValues?.forEach { composition.recordWriteOf(it) }
            composition.recompose()//main
        }
    ) composition else null
}

CompositionImpl.recompose

override fun recompose(): Boolean = synchronized(lock) {
    drainPendingModificationsForCompositionLocked()
    //main
    composer.recompose().also { shouldDrain ->
        // Apply would normally do this for us; do it now if apply shouldn't happen.
        if (!shouldDrain) drainPendingModificationsLocked()
    }
}

ComposerImpl.recompose

internal fun recompose(): Boolean {
    check(changes.isEmpty()) { "Expected applyChanges() to have been called" }
    if (invalidations.isNotEmpty()) {
        trace("Compose:recompose") {
            nodeIndex = 0
            var complete = false
            val wasComposing = isComposing
            isComposing = true
            try {
                startRoot()
                skipCurrentGroup()//main
                endRoot()
                complete = true
override fun skipCurrentGroup() {
    if (invalidations.isEmpty()) {
        skipGroup()
    } else {
        val reader = reader
        val key = reader.groupKey
        val dataKey = reader.groupObjectKey
        updateCompoundKeyWhenWeEnterGroup(key, dataKey)
        startReaderGroup(reader.isNode, null)
        recomposeToGroupEnd()//main
        reader.endGroup()
        updateCompoundKeyWhenWeExitGroup(key, dataKey)
    }
}
/**
 * Recompose any invalidate child groups of the current parent group. This should be called
 * after the group is started but on or before the first child group. It is intended to be
 * called instead of [skipReaderToGroupEnd] if any child groups are invalid. If no children
 * are invalid it will call [skipReaderToGroupEnd].
 */
private fun recomposeToGroupEnd() {
        var firstInRange = invalidations.firstInRange(reader.currentGroup, end)
        while (firstInRange != null) {
            .....
        firstInRange.scope.compose(this)//main

RecomposeScopeImpl.compose

fun compose(composer: Composer) {
    block?.invoke(composer, 1) ?: error("Invalid restart scope")//这里的block为前一次composition时通过updateScope设置进来的block也就是lambdaSubClass
}

CompositionImpl.applyChanges

override fun applyChanges() {
    synchronized(lock) {
        composer.applyChanges()
        drainPendingModificationsLocked()
    }
}

ComposerImpl.applyChanges

internal fun applyChanges() {
    trace("Compose:applyChanges") {
        val invalidationAnchors = slotTable.read { reader ->
            invalidations.map { reader.anchor(it.location) to it }
        }

        val manager = RememberEventDispatcher(abandonSet)
        try {
            applier.onBeginChanges()

            // Apply all changes
            slotTable.write { slots ->
                val applier = applier
                changes.forEach { change ->
                    change(applier, slots, manager)//main
                }
                changes.clear()
            }