Compose 原理解析

Compose 的组件都是放在 setContent() 之后才能显示的,那需要先看看这个函数的作用。

先看 ComponentActivity 的扩展函数 setContent():

    /*** 将给定的可组合项合成到给定的 Activity 中。[content] 将成为给定 Activity 的根视图。* 这大致相当于使用一个 [ComposeView] 调用 [ComponentActivity.setContentView]:** setContentView(*   ComposeView(this).apply {*     setContent {*       MyComposableContent()*     }*   }* )* * @param parent 父组合(parent composition)的引用,用于协调组合更新的调度* @param content 一个声明 UI 内容的 @Composable 函数*/ public fun ComponentActivity.setContent(parent: CompositionContext? = null,content: @Composable () -> Unit) {// 将 DecorView 中 id 为 android.R.id.content 的 ViewGroup 的第一个子组件转换成 ComposeViewval existingComposeView = window.decorView.findViewById<ViewGroup>(android.R.id.content).getChildAt(0) as? ComposeViewif (existingComposeView != null) with(existingComposeView) {// 此处调用的是 ComposeView 的 setContent(),因此不会形成递归setParentCompositionContext(parent)setContent(content)} else ComposeView(this).apply {// Set content and parent **before** setContentView// to have ComposeView create the composition on attachsetParentCompositionContext(parent)setContent(content)// Set the view tree owners before setting the content view so that the inflation process// and attach listeners will see them already present// AppCompat 1.3+ 版本之前由于 bug 不会自动设置 Owners,所以才在这里手动设置,属于修复 bug 的代码setOwners()// 设置当前的 ComposeView 为 Activity 的显示内容setContentView(this, DefaultActivityContentLayoutParams)}}

setContent() 的主要任务是将用户写的 Compose 组件设置为 Activity 的显示内容。具体说来,就是将 Compose 组件封装进 ComposeView 中,然后用 ComponentActivity 的 setContentView() 设置当前 Activity 显示这个 ComposeView。在这个过程中,通过复用现有的 ComposeView —— existingComposeView 避免重复创建从而优化了性能。

关于 existingComposeView,是将 DecorView 中的 android.R.id.content 对应的 ViewGroup 的第一个子组件强转为 ComposeView。熟悉原生的 Activity 布局层级的朋友们应该知道,android.R.id.content 对应的 ViewGroup 就是显示 Activity 的整体内容的,也就是显示原生的 XML 布局的。通过 getChildAt(0) 去拿它的第一个子组件,实际上拿到的就是 XML 布局中的根组件。那现在使用 Compose 没有 XML 了,所以就用被设置到 setContentView() 中的 this —— ComposeView 平替 XML 的根组件了。

首次调用 setContent() 的 existingComposeView 应该是 null,因为还没通过 setContentView() 设置要显示的内容,因此 getChildAt(0) 拿到的是 null。所以继续走后续代码,就会进入 else,创建一个新的 ComposeView 并将其传给 setContentView(),在这之后 existingComposeView 就是传入的 ComposeView 对象,也就不再是 null 了,后续如果发生配置变化(如屏幕旋转),重新创建 Activity 再次进入 setContent() 时,existingComposeView 可以被复用而无需重新创建 ComposeView。

接下来关注 if 和 else 中都调用了的两个函数 —— ComposeView 的 setParentCompositionContext() 和 setContent()。前者的作用是设置父组合上下文(用于管理 Composition 的作用域和生命周期),后者的作用是更新 Compose UI 内容,触发重组。

setParentCompositionContext() 是 ComposeView 的父类 AbstractComposeView 中的函数:

    /*** 设置应该作为此视图的组合(view's composition)的父级的 CompositionContext。如果 parent 为 null,* 则将自动从视图所依附到的窗口(the window the view is attached to)确定。*/fun setParentCompositionContext(parent: CompositionContext?) {parentContext = parent}

CompositionContext 用于连接父组合与子组合,参数的 parent 是由 setContent() 的第一个参数传到这里的。由于调用 setContent() 时通常不会给 parent 参数赋值,因此它会取默认值 null,所以这里给 parentContext 赋的就是 null。至于 parentContext 属性的具体内容,我们暂时还用不到,因此暂时搁置。

然后我们再看 ComposeView 的 setContent():

    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)/*** 设置此视图的 Jetpack Compose UI 内容。当视图附加到窗口或调用 createComposition 时,将发生* 初始的组合(initial composition),以先到者为准。* @param content 就是在 Activity 的 onCreate() 中调用的 setContent() 的尾随 lambda 函数,页面内容*/fun setContent(content: @Composable () -> Unit) {// 这个变量表示是否应该在附加到窗口时创建新的组合,后面会用到shouldCreateCompositionOnAttachedToWindow = true// 将参数传入的表示 UI 内容的 Composable 函数保存到 content 中this.content.value = content// 我们现在看的是 onCreate() 中的流程,还没到 onAttachedToWindow(),因此此时 isAttachedToWindow // 为 false,不会进入 if,但是 createComposition() 内的主要逻辑 ensureCompositionCreated()// 会在附加到 Window 的过程 onAttachedToWindow() 中被调用if (isAttachedToWindow) {createComposition()}}

setContent() 流程走完了,并没有看出如何显示 Compose 的组件内容的。这是因为 ComposeView 作为一个 ViewGroup 的子类,也就是 View 的子类,它是在附加到窗口,也就是在重写 onAttachedToWindow() 的逻辑时,才需要显示 UI 内容。

    // AbstractComposeView 直接继承 ViewGroup 并重写了 onAttachedToWindow()override fun onAttachedToWindow() {super.onAttachedToWindow()// 记录与当前页面绑定的 WindowpreviousAttachedWindowToken = windowTokenif (shouldCreateCompositionOnAttachedToWindow) {ensureCompositionCreated()}}

由于 shouldCreateCompositionOnAttachedToWindow 在 onCreate() 内调用 setContent() 的流程中,执行到 ComposeView 的 setContent() 时已经被设为 true 了,所以这里可以进入 if 执行 ensureCompositionCreated():

    private fun ensureCompositionCreated() {// 还没开始组合过程,也就不会有组合结果,再到当前流程中 composition 为 nullif (composition == null) {try {// 标记,是否正在创建组合creatingComposition = true// 进行组合过程并返回组合结果composition = setContent(resolveParentCompositionContext()) {Content()}} finally {// 创建完组合,清除标记creatingComposition = false}}}

这里调用的是父类 AbstractComposeView 的 setContent 函数(双参),而不是此前看过的 ComposeView 的 setContent 函数(单参,并且父类中也没办法调用子类的函数,除非就是子类对象调用了一个父子都有的函数):

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).also { addView(it.view, DefaultLayoutParams) }return doSetContent(composeView, parent, content)
}

我们先看 ensureCompositionCreated() 给 setContent() 传的实参 resolveParentCompositionContext():

    private fun resolveParentCompositionContext() = parentContext?: findViewTreeCompositionContext()?.cacheIfAlive()?: cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive }?: windowRecomposer.cacheIfAlive()

parentContext 在看 ComponentActivity.setContent() 的流程时出现过,在 setParentCompositionContext() 内被赋值,是 null。

再看 findViewTreeCompositionContext():

/*** 返回此视图层级结构中当前节点的父组合上下文(parent CompositionContext),如果未找到则返回 null。* 如需获取或设置特定视图的父组合上下文,请参阅 compositionContext。*/
fun View.findViewTreeCompositionContext(): CompositionContext? {var found: CompositionContext? = compositionContextif (found != null) return foundvar parent: ViewParent? = parent// 一直向上找父组件的 CompositionContextwhile (found == null && parent is View) {found = parent.compositionContextparent = parent.getParent()}return found
}

那这个函数的返回值到底如何呢?取决于被查找的 View 的 compositionContext 属性:

/*** The [CompositionContext] that should be used as a parent for compositions at or below* this view in the hierarchy. Set to non-`null` to provide a [CompositionContext]* for compositions created by child views, or `null` to fall back to any [CompositionContext]* provided by ancestor views.**/
var View.compositionContext: CompositionContext?get() = getTag(R.id.androidx_compose_ui_view_composition_context) as? CompositionContextset(value) {setTag(R.id.androidx_compose_ui_view_composition_context, value)}

在没有给 View 设置 id 为 androidx_compose_ui_view_composition_context 的 Tag 的情况下,compositionContext 属性为 null。通常情况下,我们都不会设置该 id 的 Tag,所以一般为 null。

继续向后,假如 findViewTreeCompositionContext() 找到了 CompositionContext,那么就执行 cacheIfAlive():

    /*** 如果这个 CompositionContext 处于活跃状态,将它缓存到 cachedViewTreeCompositionContext 中同时* 返回自身 */private fun CompositionContext.cacheIfAlive(): CompositionContext = also { context ->context.takeIf { it.isAlive }?.let { cachedViewTreeCompositionContext = WeakReference(it) }}

接下来就是去取缓存 cachedViewTreeCompositionContext,在这个缓存的 CompositionContext 处于活跃状态时返回它。

最后检查 windowRecomposer 并在它处于活跃状态时缓存它,它也是 View 的扩展属性:

/*** Get or lazily create a [Recomposer] for this view's window. The view must be attached* to a window with the [LifecycleOwner] returned by [findViewTreeLifecycleOwner] registered at* the root to access this property.*/
@OptIn(InternalComposeUiApi::class)
internal val View.windowRecomposer: Recomposerget() {check(isAttachedToWindow) {"Cannot locate windowRecomposer; View $this is not attached to a window"}val rootView = contentChildreturn when (val rootParentRef = rootView.compositionContext) {null -> WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)is Recomposer -> rootParentRefelse -> error("root viewTreeParentCompositionContext is not a Recomposer")}}

先看 contentChild 是什么:

/*** Find the "content child" for this view. The content child is the view that is either* a direct child of the view with id [android.R.id.content] (and was therefore set as a* content view into an activity or dialog window) or the root view of the window.** This is used as opposed to [View.getRootView] as the Android framework can reuse an activity* window's decor views across activity recreation events. Since a window recomposer is associated* with the lifecycle of the host activity, we want that recomposer to shut down and create a new* one for the new activity instance.*/
private val View.contentChild: Viewget() {var self: View = thisvar parent: ViewParent? = self.parentwhile (parent is View) {if (parent.id == android.R.id.content) return selfself = parentparent = self.parent}return self}

就是 android.R.id.content 那个 ViewGroup 的子组件,也就是我们在 ComponentActivity.setContent() 中看到的那个 ComposeView —— existingComposeView。

将 contentChild 属性赋值给 rootView 后,根据 rootView.compositionContext 决定哪一条分支,由于前面已经提到过,现在所有 View 的 CompositionContext 都是 null,因此要调用 WindowRecomposerPolicy.createAndInstallWindowRecomposer():

@InternalComposeUiApi
object WindowRecomposerPolicy {private val factory = AtomicReference<WindowRecomposerFactory>(WindowRecomposerFactory.LifecycleAware)internal fun createAndInstallWindowRecomposer(rootView: View): Recomposer {val newRecomposer = factory.get().createRecomposer(rootView)rootView.compositionContext = newRecomposer// 界面退出后做清理收尾工作的代码,省略...return newRecomposer}
}

很明显,这个函数是创建了一个 CompositionContext 对象 newRecomposer,并在返回它之前赋值给 rootView 的 compositionContext 属性。到这里,rootView 的 compositionContext 终于不为空了。

下面我们要看一下 createRecomposer() 是如何创建 CompositionContext 的,点进函数源码发现是 WindowRecomposerFactory 接口的抽象函数:

/*** A factory for creating an Android window-scoped Recomposer. See createRecomposer.*/
@InternalComposeUiApi
fun interface WindowRecomposerFactory {/*** Get a [Recomposer] for the window where [windowRootView] is at the root of the window's* [View] hierarchy. The factory is responsible for establishing a policy for* [shutting down][Recomposer.cancel] the returned [Recomposer]. [windowRootView] will* hold a hard reference to the returned [Recomposer] until it [joins][Recomposer.join]* after shutting down.*/fun createRecomposer(windowRootView: View): Recomposercompanion object {/*** A [WindowRecomposerFactory] that creates **lifecycle-aware** [Recomposer]s.** Returned [Recomposer]s will be bound to the* [LifecycleOwner] returned by [findViewTreeLifecycleOwner] registered* at the [root][View.getRootView] of the view hierarchy and run* [recomposition][Recomposer.runRecomposeAndApplyChanges] and composition effects on the* [AndroidUiDispatcher.CurrentThread] for the window's UI thread. The associated* [MonotonicFrameClock] will only produce frames when the [Lifecycle] is at least* [Lifecycle.State.STARTED], causing animations and other uses of [MonotonicFrameClock]* APIs to suspend until a **visible** frame will be produced.*/@OptIn(ExperimentalComposeUiApi::class)val LifecycleAware: WindowRecomposerFactory = WindowRecomposerFactory { rootView ->rootView.createLifecycleAwareWindowRecomposer()}}
}

此时就要看 factory.get() 拿到的是什么对象,通过 factory 的定义可以确定 get() 得到的是一个 LifecycleAware,createLifecycleAwareWindowRecomposer() 会返回一个已经准备好(界面刷新)的工具 Recomposer(代码很复杂,这里不贴)。

所以,resolveParentCompositionContext() 最终返回的是一个用于等待和准备刷新的工具 Recomposer。

再看 setContent() 的第二个参数,传的是 AbstractComposeView 的抽象函数 Content():

    @Composable@UiComposableabstract fun Content()

在它的子类 ComposeView 中的实现就是调用此前保存在 content 属性中的函数:

    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)@Composableoverride fun Content() {content.value?.invoke()}

把 Content() 放入 lambda 表达式中作为 setContent() 的第二个参数,何时执行就要看该函数的具体内容了:

/*** Composes the given composable into the given view.** The new composition can be logically "linked" to an existing one, by providing a* [parent]. This will ensure that invalidations and CompositionLocals will flow through* the two compositions as if they were not separate.** Note that this [ViewGroup] should have an unique id for the saved instance state mechanism to* be able to save and restore the values used within the composition. See [View.setId].** @param parent The [Recomposer] or parent composition reference.* @param content Composable that will be the content of the view.*/
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).also { addView(it.view, DefaultLayoutParams) }return doSetContent(composeView, parent, content)
}

GlobalSnapshotManager.ensureStarted():

/*** Platform-specific mechanism for starting a monitor of global snapshot state writes* in order to schedule the periodic dispatch of snapshot apply notifications.* This process should remain platform-specific; it is tied to the threading and update model of* a particular platform and framework target.** Composition bootstrapping mechanisms for a particular platform/framework should call* [ensureStarted] during setup to initialize periodic global snapshot notifications.* For Android, these notifications are always sent on [AndroidUiDispatcher.Main]. Other platforms* may establish different policies for these notifications.*/
internal object GlobalSnapshotManager {private val started = AtomicBoolean(false)fun ensureStarted() {if (started.compareAndSet(false, true)) {val channel = Channel<Unit>(Channel.CONFLATED)CoroutineScope(AndroidUiDispatcher.Main).launch {// 订阅通知channel.consumeEach {// 通知回调,收到通知后的行为Snapshot.sendApplyNotifications()}}Snapshot.registerGlobalWriteObserver {// 发送通知channel.trySend(Unit)}}}
}

通知回调的行为是去执行 Snapshot.sendApplyNotifications(),主要作用是通知更新,这是一个很复杂的过程。Snapshot 系统是用来管理 Compose 组件依赖的变量的,它会在这些变量发生变化时自动感知到这些变量的变化,并把这些变化应用到界面中,保证使用这些变量最新的值去及时的刷新界面。此外,Snapshot 的复杂还因为它支持了多线程,可以在多个线程中去更新 Compose 组件依赖的变量,而原生只能在主线程中更新 UI。

sendApplyNotifications() 只说关键代码:

sealed class Snapshot {companion object {fun sendApplyNotifications() {val changes = sync {currentGlobalSnapshot.get().modified?.isNotEmpty() == true}if (changes)advanceGlobalSnapshot()}}
}

当组件依赖的变量有变化时,调用 advanceGlobalSnapshot():

private fun advanceGlobalSnapshot() = advanceGlobalSnapshot { }private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {var previousGlobalSnapshot = snapshotInitializer as GlobalSnapshotval result = sync {previousGlobalSnapshot = currentGlobalSnapshot.get()takeNewGlobalSnapshot(previousGlobalSnapshot, block)}// If the previous global snapshot had any modified states then notify the registered apply// observers.val modified = previousGlobalSnapshot.modifiedif (modified != null) {val observers: List<(Set<Any>, Snapshot) -> Unit> = sync { applyObservers.toMutableList() }// 把界面中用到的发生变化的变量进行新值的应用,确切地说,把界面中使用到的发生变量变化的部分标记为失效,这样// 失效的部分就会发生重组,然后再发生布局和绘制,完整整个页面的刷新。只标记发生变化的部分是为了节省性能,只刷新// 应该刷新的地方保证耗时最短,刷新效率最高observers.fastForEach { observer ->observer(modified, previousGlobalSnapshot)}}sync {modified?.forEach(::overwriteUnusedRecordsLocked)}return result
}

applyObservers 是在 被添加的:

        // 还是在 Snapshot 的伴生对象中fun registerApplyObserver(observer: (Set<Any>, Snapshot) -> Unit): ObserverHandle {// Ensure observer does not see changes before this call.advanceGlobalSnapshot(emptyLambda)sync {// 向 applyObservers 添加监听者applyObservers.add(observer)}return ObserverHandle {sync {applyObservers.remove(observer)}}}

在 Recomposer 的 recompositionRunner() 中调用了 registerApplyObserver():

    private suspend fun recompositionRunner(block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit) {val parentFrameClock = coroutineContext.monotonicFrameClockwithContext(broadcastFrameClock) {// Enforce mutual exclusion of callers; register self as current runnerval callingJob = coroutineContext.jobregisterRunnerJob(callingJob)// Observe snapshot changes and propagate them to known composers only from// this caller's dispatcher, never working with the same composer in parallel.// unregisterApplyObserver is called as part of the big finally below// 注册回调,收到回调之后,通过 resume() 开始执行重组工作val unregisterApplyObserver = Snapshot.registerApplyObserver { changed, _ ->synchronized(stateLock) {if (_state.value >= State.Idle) {snapshotInvalidations.addAll(changed)deriveStateLocked()} else null}?.resume(Unit)}addRunning(recomposerInfo)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)}} finally {unregisterApplyObserver.dispose()synchronized(stateLock) {if (runnerJob === callingJob) {runnerJob = null}deriveStateLocked()}removeRunning(recomposerInfo)}}}

回到 sendApplyNotifications(),该函数就是触发重组的。然后再往下看 registerGlobalWriteObserver(),该函数是对变量写行为的监听,当变量有变化时就会执行一次后面的 lambda,通过 channel 发送一次通知。每次发通知,都会让接收端执行 consumeEach() 后的 lambda,也就是触发重组。这里利用 Channel 的特性对重组触发次数做了优化,一帧中只会触发一或两次重组,从而避免导致高频修改引发高频刷新的性能问题。

再看 AbstractComposeView 的第二部分,生成一个 AndroidComposeView 并将其添加到视图中,形成一个 ComposeView 包含 AndroidComposeView 的结构。实际上,AndroidComposeView 才是负责显示与触摸反馈(真正干活的)的那个 View。

最后一部分是 doSetContent():

private fun doSetContent(owner: AndroidComposeView,parent: CompositionContext,content: @Composable () -> Unit
): Composition {...// 创建一个 Composition 对象,实际类型为 CompositionImplval original = Composition(UiApplier(owner.root), parent)// 先通过 Tag 去拿 WrappedComposition,如果拿到的为空则自行创建一个val wrapped = owner.view.getTag(R.id.wrapped_composition_tag)as? WrappedComposition?: WrappedComposition(owner, original).also {owner.view.setTag(R.id.wrapped_composition_tag, it)}wrapped.setContent(content)return wrapped
}

调用 WrappedComposition 的 setContent():

    override fun setContent(content: @Composable () -> Unit) {// 约等于对 onAttachedToWindow() 的监听owner.setOnViewTreeOwnersAvailable {if (!disposed) {val lifecycle = it.lifecycleOwner.lifecyclelastContent = content// 第一次进入 if,第二次进入 else ifif (addedToLifecycle == null) {addedToLifecycle = lifecycle// this will call ON_CREATE synchronously if we already createdlifecycle.addObserver(this)} else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {// original 是 CompositionImploriginal.setContent {@Suppress("UNCHECKED_CAST")val inspectionTable =owner.getTag(R.id.inspection_slot_table_set) as?MutableSet<CompositionData>?: (owner.parent as? View)?.getTag(R.id.inspection_slot_table_set)as? MutableSet<CompositionData>if (inspectionTable != null) {inspectionTable.add(currentComposer.compositionData)currentComposer.collectParameterInformation()}LaunchedEffect(owner) { owner.keyboardVisibilityEventLoop() }LaunchedEffect(owner) { owner.boundsUpdatesEventLoop() }CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {ProvideAndroidCompositionLocals(owner, content)}}}}}}

看 CompositionImpl 的 setContent():

    override fun setContent(content: @Composable () -> Unit) {check(!disposed) { "The composition is disposed" }// 又把最外面的 setContent() 后的 lambda 保存了一次this.composable = content// parent 类型是 CompositionContext,实际上是前面的 Recomposer,用于刷新的,不是用于初始化的,在 doSetContent()// 内创建 Composition 时作为第二个参数传入parent.composeInitial(this, composable)}

既然 parent 是 Recomposer,那么自然就要去看 Recomposer 的 composeInitial():

    internal override fun composeInitial(composition: ControlledComposition,content: @Composable () -> Unit) {val composerWasComposing = composition.isComposingtry {composing(composition, null) {composition.composeContent(content)}} catch (e: Exception) {processCompositionError(e, composition, recoverable = true)return}// TODO(b/143755743)if (!composerWasComposing) {Snapshot.notifyObjectsInitialized()}synchronized(stateLock) {if (_state.value > State.ShuttingDown) {if (composition !in knownCompositions) {knownCompositions += composition}}}try {performInitialMovableContentInserts(composition)} catch (e: Exception) {processCompositionError(e, composition, recoverable = true)return}try {composition.applyChanges()composition.applyLateChanges()} catch (e: Exception) {processCompositionError(e)return}if (!composerWasComposing) {// Ensure that any state objects created during applyChanges are seen as changed// if modified after this call.Snapshot.notifyObjectsInitialized()}}

在 composing() 的尾随 lambda 中执行 composition.composeContent(),这样会取执行 CompositionImpl.composeContent() -> ComposerImpl.composeContent() -> ComposerImpl.doCompose():

    private fun doCompose(invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,content: (@Composable () -> Unit)?) {runtimeCheck(!isComposing) { "Reentrant composition is not supported" }trace("Compose:recompose") {snapshot = currentSnapshot()compositionToken = snapshot.idproviderUpdates.clear()invalidationsRequested.forEach { scope, set ->val location = scope.anchor?.location ?: returninvalidations.add(Invalidation(scope, location, set))}invalidations.sortBy { it.location }nodeIndex = 0var complete = falseisComposing = truetry {startRoot()// vv Experimental for forced@Suppress("UNCHECKED_CAST")val savedContent = nextSlot()if (savedContent !== content && content != null) {updateValue(content as Any?)}// ^^ Experimental for forced// Ignore reads of derivedStateOf recalculationsobserveDerivedStateRecalculations(start = {childrenComposing++},done = {childrenComposing--},) {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} finally {isComposing = falseinvalidations.clear()if (!complete) abortRoot()}}}

invokeComposable() 执行的是 ActualJvm.jvm.kt 中的函数:

internal actual fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {@Suppress("UNCHECKED_CAST")val realFn = composable as Function2<Composer, Int, Unit>realFn(composer, 1)
}

composable 参数就是 setContent() 的尾随 lambda 指定的 Compose 组件内容,它被强转为 Function2,是因为 Compose 编译器插件会对所有 @Composable 函数添加两个参数。最后调用转换成 Function2 的 realFn(),也就是执行 lambda 内的内容了。

以上就是 setContent() 的内容,它并没有对界面的显示做任何工作,它所做的就是布置好各种监听器,以便变量发生变化时触发界面刷新。真正负责显示的是各个组件 Composable 函数内的通用函数 Layout():

@UiComposable
@Composable inline fun Layout(content: @Composable @UiComposable () -> Unit,modifier: Modifier = Modifier,measurePolicy: MeasurePolicy
) {val density = LocalDensity.currentval layoutDirection = LocalLayoutDirection.currentval viewConfiguration = LocalViewConfiguration.currentReusableComposeNode<ComposeUiNode, Applier<Any>>(factory = ComposeUiNode.Constructor,// 这里预先设置要做的处理策略update = {set(measurePolicy, ComposeUiNode.SetMeasurePolicy)set(density, ComposeUiNode.SetDensity)set(layoutDirection, ComposeUiNode.SetLayoutDirection)set(viewConfiguration, ComposeUiNode.SetViewConfiguration)},skippableUpdate = materializerOf(modifier),content = content)
}
@Composable @ExplicitGroupsComposable
inline fun <T, reified E : Applier<*>> ReusableComposeNode(noinline factory: () -> T,update: @DisallowComposableCalls Updater<T>.() -> Unit,noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,content: @Composable () -> Unit
) {if (currentComposer.applier !is E) invalidApplier()currentComposer.startReusableNode()if (currentComposer.inserting) {// 创建 LayoutNode 节点currentComposer.createNode(factory)} else {currentComposer.useNode()}// 更新 LayoutNode 节点Updater<T>(currentComposer).update()SkippableUpdater<T>(currentComposer).skippableUpdate()currentComposer.startReplaceableGroup(0x7ab4aae9)content()currentComposer.endReplaceableGroup()currentComposer.endNode()
}
    // ComposerImpl:@Suppress("UNUSED")override fun <T> createNode(factory: () -> T) {validateNodeExpected()runtimeCheck(inserting) { "createNode() can only be called when inserting" }val insertIndex = nodeIndexStack.peek()val groupAnchor = writer.anchor(writer.parent)groupNodeCount++recordFixup { applier, slots, _ ->@Suppress("UNCHECKED_CAST")// 创建 LayoutNode,看参数来源,是 ReusableComposeNode() 的 factory 参数val node = factory()// LayoutNode 装进 SlotTable 中slots.updateNode(groupAnchor, node)@Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<T>nodeApplier.insertTopDown(insertIndex, node)applier.down(node)}recordInsertUpFixup { applier, slots, _ ->@Suppress("UNCHECKED_CAST")val nodeToInsert = slots.node(groupAnchor)applier.up()@Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<Any?>// 将 LayoutNode 节点插进 LayoutNode 树中(树是组合的最终结果)nodeApplier.insertBottomUp(insertIndex, nodeToInsert)}}

SlotTable 是一种数据结构,用于存储 Compose 的 LayoutNode 树以及这个树上用到的变量。在显示界面时,是用不到 SlotTable 的。它在最底层是用一维数组实现了 LayoutNode 树的存储,而对于各个嵌套的 LayoutNode 之间是使用链表将它们连接起来的。使用它的目的是为了提升性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/38160.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型-提示词工程与架构

什么是提示工程 提示工程&#xff08;Prompt Engineering&#xff09;是一门新兴的技术领域&#xff0c;专注于研究如何设计、构建和优化提示词&#xff0c;以充分发挥大模型的潜力 。它涉及到对语言结构、任务需求、模型特性等多方面因素的综合考量。提示工程的目标是通过精心…

Agent Team 多智能体系统解析

引言 在人工智能技术高速发展的今天&#xff0c;"多智能体协作系统"&#xff08;Agent Team&#xff09;正成为突破效率瓶颈的关键技术。与传统的单体AI不同&#xff0c;这种由多个专业化智能体组成的协同网络&#xff0c;通过分工协作和动态调整&#xff0c;展现出…

【蓝桥杯—单片机】IAP15F2K61S2专项 | 真题整理、解析与拓展 | 省赛题(更新ing...)

IAP15F2K61S2 专项 前言IAP15F2K61S2 介绍&#xff08;基于手册&#xff09;I/O口结构复位管脚RST中断第十四届省赛 外设通过PWM控制第十五届省赛题 性能与工作参数在线调试第十四届省赛题拓展与小结&#xff1a;单片机在线调试常用的接口 功耗第十五届省赛题 前言 在本文中我…

生物化学笔记:医学免疫学原理02 抗原概念+免疫应答+抗原的分类

抗原基本概念 影响抗原刺激机体产生免疫应答的因素 抗原的分类 CG 【北京大学】1080p 王月丹教授 《医学免疫学原理》2022春 全81p

(UI自动化测试)第二篇:元素定位的方法_name定位

二、name定位 ⽅法&#xff1a; driver.find_element_by_name(“name属性值”) 前置&#xff1a; 标签必须name属性 特点&#xff1a; 当前⻚⾯可以重复 提示&#xff1a; 由于name属性值可以重复&#xff0c;所以使⽤时需要查看是否为唯⼀。 # 导包selenium from selenium i…

软考中级-软件设计师 准备

软考中级-软件设计师 准备 一、软考相关1.1、考试时间1.2、考试时长1.3、题型和分值&#xff1a; 二、软考备考2.1、相关书籍2.2、推荐课程&#xff1a;B站up主zst_20012.3、学习路线 一、软考相关 1.1、考试时间 一年有两次软考&#xff0c;一般是五月末和十一月的中旬 以下…

记忆力训练day24

一 数字锁链串联法 数字两位 两位的连

田间机器人幼苗视觉检测与护苗施肥装置研究(大纲)

田间机器人幼苗视觉检测与护苗施肥装置研究 基于多光谱视觉与精准施肥的农业机器人系统设计 第一章 绪论 1.1 研究背景与意义 农业智能化需求&#xff1a; 传统幼苗检测依赖人工&#xff0c;效率低且易遗漏弱苗/病苗施肥不精准导致资源浪费和环境污染 技术挑战&#xff1a;…

Debian12生产环境配置笔记

在 Debian 12 上进行生产环境配置的详细步骤&#xff0c;涵盖软件更新、基础软件安装、Docker 及 Redis 部署&#xff0c;以及 Nginx 配置多个虚拟主机等内容。所有命令均以 root 用户身份执行&#xff0c;无需添加 sudo 1. 更新软件 首先&#xff0c;确保系统上的所有软件包…

HAL库编程知识点---Can.c和Driver_can.c分层开发

在一个工程中&#xff0c;通常会把对CAN外设的操作分成底层和上层两个部分&#xff0c;从而提高代码的模块化和可维护性。一般来说&#xff1a; can.c 通常由硬件抽象层&#xff08;HAL&#xff09;或者自动生成工具&#xff08;如 CubeMX&#xff09;提供或生成。主要负责CAN硬…

7. 【Vue实战--孢子记账--Web 版开发】-- 收支分类设置

本篇文章我们一起来实现收支分类功能。收支分类和前篇文章的主币种设置界面大体类似。我们将详细介绍如何创建和管理不同的收支分类&#xff0c;以便用户可以更好地组织和跟踪他们的财务状况。 一、功能 先来看一下原型界面&#xff0c;界面很简单&#xff0c;这里就不多讲解…

人工智能 - DeepSeek 和 Manus 的区别和应用场景

DeepSeek 与 Manus 是人工智能领域两种不同技术路线的代表,其核心区别在于功能定位和技术实现,应用场景也因此存在显著差异。以下是两者的对比分析: 一、核心区别 技术定位 DeepSeek:定位为“超级大脑”,专注于底层大模型的研发,擅长处理数学题、代码生成、知识问答等需要…

基于yolov11的防震锤缺陷检测系统python源码+pytorch模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv11的防震锤缺陷检测系统是一种利用深度学习技术进行自动化检测的系统。防震锤是电力线路中用于防止导线因风力等因素引起振动的关键部件&#xff0c;其性能状态直接影响到电力线路的安全运行。然而&#xff0c;防震锤在使用过程中可能会因各种因素导致缺…

MySQL数据库精研之旅第二期:库操作的深度探索

专栏&#xff1a;MySQL数据库成长记 个人主页&#xff1a;手握风云 目录 一、查看数据库 二、创建数据库 2.1. 语法 2.2. 示例 三、字符集编码和校验(排序)规则 3.1. 查看数据库支持的字符集编码 3.2. 查看数据库支持的排序规则 3.3. 不同的字串集与排序规则对数据库的…

ubuntu系统/run目录不能执行脚本问题解决

目录 前言 一、问题现象 二、原因分析 三、解决办法 总结 前言 在使用 Ubuntu 系统的过程中&#xff0c;我们有时会遇到在 /run 目录下无法执行脚本的情况。这篇博客将详细探讨该问题的原因&#xff0c;并提供有效的解决方案。。 一、问题现象 当尝试在 /run 目录下执行一个…

万用表测MOS好坏

测N MOS好坏 1&#xff0c;首先用万用表表笔把G D S全部短接放电。 2&#xff0c;万用表打到二极管档位 3&#xff0c;红笔接S&#xff08;源极&#xff09;&#xff0c;黑笔接D&#xff08;漏极&#xff09;&#xff0c;万用表会显示0.5V左右的电压&#xff08;内部二极管压降…

clamav服务器杀毒(Linux服务器断网状态下如何进行clamav安装、查杀)

ClamAV服务器杀毒&#xff08;服务器断网状态也可以使用该方法&#xff09; 服务器因为挖矿病毒入侵导致断网&#xff0c;进行离线的clamav安装并查杀 安装包下载网址&#xff1a;https://www.clamav.net/downloads 安装.deb&#xff0c;如果服务器处于断网状态&#xff0c;可以…

Linux:基础IO---文件描述符

文章目录 1. 前言1.1 C语言文件知识回顾 2. 文件2.1 文件基础知识 3. 被打开的文件3.1 以C语言为主&#xff0c;先回忆一下C文件接口3.2 过渡到系统&#xff0c;认识文件系统调用3.3 访问文件的本质3.4 重定向&&缓冲区 序&#xff1a;在深入了解了进程的内容后&#xf…

LINUX基础 [二] - 进程概念

目录 前言 什么是进程 如何管理进程 描述进程 组织进程 如何查看进程 通过 ps 命令查看进程 通过 ls / proc 命令查看进程 通过系统调用 获取进程标示符 前言 在学习了【Linux系统编程】中的 ​ 操作系统 和 冯诺依曼体系结构 之后&#xff0c;我们已经对系统应该有…

word使用自带的公式

文章目录 Word公式中word公式快捷键&#xff1a;word2016公式框输入多行word 公式加入空格&#xff1a;word公式如何输入矩阵:公式图片转为Latex语法word 能直接输入 latex 公式么 word文本中将文字转为上标的快捷键 Tips几个好用的网站&#xff1a; 适用于&#xff1a;我的wor…