@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);
}
}
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));
}
}
/**
* 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
}
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
}
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)
}
}
/**
* 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
}
}
/**
* 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
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
/**
* 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
}
/** 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.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
}
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
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)
}
override fun invalidateAll() {
synchronized(lock) {
composer.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() }
}
override fun invalidate() {
invalidateForResult()
}
fun invalidateForResult(): InvalidationResult =
(composer as? ComposerImpl)?.invalidate(this) ?: InvalidationResult.IGNORED
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
}
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)
}
internal override fun invalidate(composition: ControlledComposition) {
synchronized(stateLock) {
if (composition !in compositionInvalidations) {
compositionInvalidations += composition
deriveStateLocked()
} else null
}?.resume(Unit)
}
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();
}
}
}
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()
}
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) }
}
}
}
}
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()
}
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
}
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()
}
}
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
fun compose(composer: Composer) {
block?.invoke(composer, 1) ?: error("Invalid restart scope")//这里的block为前一次composition时通过updateScope设置进来的block也就是lambdaSubClass
}
override fun applyChanges() {
synchronized(lock) {
composer.applyChanges()
drainPendingModificationsLocked()
}
}
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()
}