likes
comments
collection
share

Compose编程思想 -- 深入理解Compose原理

作者站长头像
站长
· 阅读数 4

前言

其实对于Compose的原理,我在之前的文章中介绍过很多,像mutableStateOf的订阅原理、Compose中的布局和绘制原理、LayoutNode层级原理、Modifier的初始化等等,这些对于后续Compose开发其实会有很大的帮助,因为懂得了原理,才能写出高质量的代码。

那么本节我将会带大家继续深入原理,从Compose全局出发认识底层不为人知的原理。

1 Compose - setContent 原理

当我们需要写一个Compose界面的时候,通常是在setContent函数中进行组合函数的拼接,例如下面的代码:

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setContent {
            // TODO show ui
        }
    }
}    

这是系统默认帮我们生成的模板,当前Activity是继承自ComponentActivity,那么继承自AppCompatActivity或者FragmentActivity可以吗?试过之后发现不可以,而且如果新建的项目中,连AppCompatActivity的依赖都没有导入,可以说是完全摒弃了appcompat系列。

为什么只能继承ComponentActivity呢?是因为setContent内容显示是ComponentActivity的一个扩展函数,在其他Activity中就无法调用并显示页面。

1.1 ComponentActivity # setContent源码分析

所以,我会带着大家看一下setContent源码,看@Composable函数如何一步一步显示在屏幕上。

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) {
    // 核心代码1:
    val existingComposeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? ComposeView

    // 核心代码2:
    if (existingComposeView != null) with(existingComposeView) {
        setParentCompositionContext(parent)
        setContent(content)
    } else ComposeView(this).apply {
        // 核心代码3:
        // Set content and parent **before** setContentView
        // to have ComposeView create the composition on attach
        setParentCompositionContext(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
        setOwners()
        setContentView(this, DefaultActivityContentLayoutParams)
    }
}

核心代码1:

熟悉窗口伙伴应该了解,其分层结构如下:

Compose编程思想 -- 深入理解Compose原理

之前在使用AppCompactActivity的时候,一定会调用setContentView方法,将布局添加到窗口上。

@Override
public void setContentView(View v) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    contentParent.addView(v);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

setContent函数中,首先会从android.R.id.content中查找第一个子View,看是否为ComposeView,在上一节中我讲过,ComposeView是在Android原生的View体系中融入Compose UI,利用其内部提供的setContent显示组合函数。

如果没有设置过setContentView,或者第一个子View不是ComposeView类型,那么existingComposeView就为空;否则就不为空。

核心代码2:

如果不为空,那么就直接调用ComposeViewsetContentsetParentCompositionContext函数,将组合函数传递进去。

核心代码3:

如果为空,那么就自行创建一个ComposeView,也会调用ComposeViewsetContentsetParentCompositionContext函数,最终调用setContentViewComposeView添加到android.R.id.content容器中。

所以setContent的核心逻辑就是判断是否调用过setContentView函数,如果没有调用过那么就自行创建一个ComposeView,添加到窗口上。

如果调用过setContentView,而且第一个子组件是ComposeView,那么调用setContent函数就是直接在这个子组件中添加UI元素。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/composeview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

如果调用过setContentView,而且第一个子组件不是ComposeView,那么会重新创建ComposeView并覆盖之前调用setContentView的内容。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher_background"/>

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/composeview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

例如上面这种布局,ImageView将会被覆盖不会显示在屏幕上,所以需要在写布局的时候注意一下,调换一下位置,ImageView就会正常显示出来。

1.2 ComposeView # setContent源码分析

这个是当我们创建ComposeView之后,想要显示内容必须调用的函数。

// ComposeView.android.kt

/**
 * Set the Jetpack Compose UI content for this view.
 * Initial composition will occur when the view becomes attached to a window or when
 * [createComposition] is called, whichever comes first.
 */
fun setContent(content: @Composable () -> Unit) {
    shouldCreateCompositionOnAttachedToWindow = true
    this.content.value = content
    if (isAttachedToWindow) {
        createComposition()
    }
}

这个函数很简单,首先给shouldCreateCompositionOnAttachedToWindow设置为了true,然后存储传入的content,是通过mutableStateOf来存储的。

最后判断当前View是否与Window绑定,因为这个调用时机在onCreate,真正与窗口绑定是在调用了WindowManageraddView之后,这个过程是发生在onResume时,因此这个时机是没有绑定的,所以不会执行createComposition函数。

因为ComposeView是传统的View组件,所以当它被添加到窗口的时候,会执行onAttachedToWindow方法,这个是在ComposeView的父类AbstractComposeView重写的,它是一个ViewGroup


override fun onAttachedToWindow() {
    super.onAttachedToWindow()

    previousAttachedWindowToken = windowToken
    
    // 在setContent函数调用时,将其赋值为true
    if (shouldCreateCompositionOnAttachedToWindow) {
        ensureCompositionCreated()
    }
}

因为在setContent函数调用的时候,将shouldCreateCompositionOnAttachedToWindow赋值为true,因此在View添加到窗口时,会执行ensureCompositionCreated函数。

1.2.1 AbstractComposeView # ensureCompositionCreated

在初始组合的过程中,composition一定为空,因此会进入到初始化的过程中。


private var composition: Composition? = null

@Suppress("DEPRECATION") // Still using ViewGroup.setContent for now
private fun ensureCompositionCreated() {
    if (composition == null) {
        try {
            creatingComposition = true
            composition = setContent(resolveParentCompositionContext()) {
                Content()
            }
        } finally {
            creatingComposition = false
        }
    }
}

首先,我们先看下Composition是什么?你可以认为它就是用来描述界面的,是由所有的@Composable函数组成的,类似于一个ViewTree

所以当View和窗口绑定之后,需要做的一件事就是确保Composition是否已经创建了,会执行AbstarctComposeView的扩展函数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)
}

1.2.3 AbstractComposeView # setContent

在这个函数中,第一个参数是CompositionContext,它是通过resolveParentCompositionContext函数创建的,其实最终返回的是一个Recomposer对象。

/**
 * The scheduler for performing recomposition and applying updates to one or more [Composition]s.
 */
@Suppress("RedundantVisibilityModifier", "NotCloseable")
@OptIn(InternalComposeApi::class)
class Recomposer(
    effectCoroutineContext: CoroutineContext
) : CompositionContext() {
    // ......
}

Recomposer是用来重组界面的,当界面的数据(一般指mutableStateOf)发生变化时,调用这个数据所在的ComposeScope会进行重组,此时就是通过Recomposer来完成。

第二个参数content,其实就是在调用ComposeViewsetContent函数时传入的一系列组合函数,最终调用AbstractComposeViewsetContent函数来创建Composition

1.3 State刷新底层原理

前面我介绍了AbstractComposeViewsetContent函数中的两个参数,再回头看里面的实现。

internal fun AbstractComposeView.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    // 核心代码1:
    GlobalSnapshotManager.ensureStarted()
    // 核心代码2:
    val composeView =
        if (childCount > 0) {
            getChildAt(0) as? AndroidComposeView
        } else {
            removeAllViews(); null
        } ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
    return doSetContent(composeView, parent, content)
}

1.3.1 观察者注册

首先调用了GlobalSnapshotManagerensureStarted函数,我先说下这个流程的作用:就是能够感知Compose中的State值的变化,通过ReComposer触发重组,来刷新页面。

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

首先创建了一个Channel,如果不熟悉Channel的伙伴可以看下我之前的文章,它其实是属于热流的一种,能够完成协程之间的通信。

首先是调用SnapshotregisterGlobalWriteObserver函数,这里是注册了一个全局的写函数观察者。

fun registerGlobalWriteObserver(observer: ((Any) -> Unit)): ObserverHandle {
    sync {
        // globalWriteObservers是一个集合,会把observer添加到globalWriteObservers
        globalWriteObservers += observer
    }
    advanceGlobalSnapshot()
    return ObserverHandle {
        sync {
            globalWriteObservers -= observer
        }
        advanceGlobalSnapshot()
    }
}

看过我前面文章的伙伴们应该知道,当Compose中的State变量进行读写操作时,Snapshot中的读写观察者会做相应的处理操作,我再带伙伴们回顾一下,这里很重要,会跟之前的知识串联起来。

当State的值发生变化时,会调用set(value),执行StateRecordoverwritable函数对值进行复写。

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 }
            }
        }
    // ......    
}        

重点看下面这个函数overwritable

internal inline fun <T : StateRecord, R> T.overwritable(
    state: StateObject,
    candidate: T,
    block: T.() -> R
): R {
    var snapshot: Snapshot = snapshotInitializer
    return sync {
        snapshot = Snapshot.current
        this.overwritableRecord(state, snapshot, candidate).block()
    }.also {
        notifyWrite(snapshot, state)
    }
}

在这个函数中拿到的SnapshotGlobalSnapshot,它是通过AtomicReference修饰,说明会有高并发的场景。

@PublishedApi
internal val snapshotInitializer: Snapshot = currentGlobalSnapshot.get()

private val currentGlobalSnapshot = AtomicReference(
    GlobalSnapshot(
        id = nextSnapshotId++,
        invalid = SnapshotIdSet.EMPTY
    ).also {
        openSnapshots = openSnapshots.set(it.id)
    }
)

GlobalSnapshot是继承自MutableSnapshot,它构造函数第4个参数,就是writeObserver,也就是在registerGlobalWriteObserver函数调用时,传入的观察者。

internal class GlobalSnapshot(id: Int, invalid: SnapshotIdSet) :
    MutableSnapshot(
        id, invalid, null, //readObserver
        sync {
            globalWriteObservers.let {
                it.singleOrNull() ?: { state: Any ->
                    it.fastForEach { it(state) }
                }
            }
        } // writeObserver
    ) 

当State的值发生变化之后,会调用notifyWrite函数,从源码中看,就是拿到了MutableSnapshot中的writeObserver,然后执行其回调。

@PublishedApi
internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
    snapshot.writeCount += 1
    snapshot.writeObserver?.invoke(state)
}

那么再回头看,其实globalWriteObservers的回调,就是执行了channeltrySend函数,也就是说当State的值发生变化时,会通过Channel把对应的状态发送,然后在consumeEach中会接收到。

1.3.2 页面刷新

当State的值发生变化时,会通过Channel发送通知,当在consumeEach接收到通知后,会执行SnapshotsendApplyNotifications函数。

fun sendApplyNotifications() {
    val changes = sync {
        currentGlobalSnapshot.get().modified?.isNotEmpty() == true
    }
    if (changes)
        advanceGlobalSnapshot()
}

在这个函数中,首先会判断是否真的有值发生了变化,如果没有就不需要处理;如果真的发生了变化,那么就会执行advanceGlobalSnapshot函数。

private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
    var previousGlobalSnapshot = snapshotInitializer as GlobalSnapshot

    var modified: IdentityArraySet<StateObject>? = null // Effectively val; can be with contracts
    // 核心代码1:
    val result = sync {
        previousGlobalSnapshot = currentGlobalSnapshot.get()
        modified = previousGlobalSnapshot.modified
        if (modified != null) {
            pendingApplyObserverCount.add(1)
        }
        takeNewGlobalSnapshot(previousGlobalSnapshot, block)
    }

    // If the previous global snapshot had any modified states then notify the registered apply
    // observers.
    // 核心代码2:
    modified?.let {
        try {
            val observers = applyObservers
            observers.fastForEach { observer ->
                observer(it, previousGlobalSnapshot)
            }
        } finally {
            pendingApplyObserverCount.add(-1)
        }
    }

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

    return result
}

核心代码1:

这里面的代码我就不跟进了,其实就是当前Snapshot中使用过【这些发生改变的变量】的地方失效,并构建一个新的Snapshot

核心代码2:

遍历applyObservers,并执行observer回调。那么applyObservers是什么时候初始化的呢?

fun registerApplyObserver(observer: (Set<Any>, Snapshot) -> Unit): ObserverHandle {
    // Ensure observer does not see changes before this call.
    advanceGlobalSnapshot(emptyLambda)

    sync {
        applyObservers += observer
    }
    return ObserverHandle {
        sync {
            applyObservers -= observer
        }
    }
}

registerApplyObserver函数中,会将observer添加到applyObservers中,而这个函数的调用是在ComposerrecompositionRunner函数中。

@OptIn(ExperimentalComposeApi::class)
private suspend fun recompositionRunner(
    block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
) {
    val parentFrameClock = coroutineContext.monotonicFrameClock
    withContext(broadcastFrameClock) {
        // ......

        // 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
        val unregisterApplyObserver = Snapshot.registerApplyObserver { changed, _ ->
            synchronized(stateLock) {
                if (_state.value >= State.Idle) {
                    changed.fastForEach {
                        if (
                            it is StateObjectImpl &&
                                !it.isReadIn(ReaderKind.Composition)
                        ) {
                            // continue if we know that state is never read in composition
                            return@fastForEach
                        }
                        snapshotInvalidations.add(it)
                    }
                    deriveStateLocked()
                } else null
            }?.resume(Unit)
        }

        // ......
    }
}

observer执行时,会将修改过的StateObject组合,也就是所有的变量集合传入并遍历,在遍历的过程中,如果当前变量虽然被修改了,但是不会在组合中使用,就会跳过。

然后将发生变化的StateObject放在snapshotInvalidations失效集合中,遍历完成之后,执行resume将Composer挂起的协程唤醒,触发重组。

所以在创建Composition之前,会先进行State状态变化监听的初始化工作。

1.4 Composition构建

OK,再回到AbstractComposeViewsetContent函数,看Composition是如何创建的。

因为AbstractComposeView是一个ViewGroup,因此会判断其中第一个子组件是否为AndroidComposeView;如果不是,那么就会创建一个AndroidComposeView添加到AbstractComposeView当中。

然后执行doSetContent函数。

private fun doSetContent(
    owner: AndroidComposeView,
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    // .....
    
    val original = Composition(UiApplier(owner.root), parent)
    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
}

首先会创建一个Composition对象,其实从源码看就是CompositionImpl

fun Composition(
    applier: Applier<*>,
    parent: CompositionContext
): Composition =
    CompositionImpl(
        parent,
        applier
    )

然后取以R.id.wrapped_composition_tag为Tag的View,因为第一次进来肯定为空,所以会创建一个WrappedComposition对象,并把owneroriginal传递进去,调用了WrappedCompositionsetContent函数,其实最终创建的是一个WrappedComposition


// Wrapper.android.kt

override fun setContent(content: @Composable () -> Unit) {
    owner.setOnViewTreeOwnersAvailable {
        if (!disposed) {
            val lifecycle = it.lifecycleOwner.lifecycle
            lastContent = content
            if (addedToLifecycle == null) {
                addedToLifecycle = lifecycle
                // this will call ON_CREATE synchronously if we already created
                lifecycle.addObserver(this)
            } else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
               // 内部又是一个setContent
               original.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.boundsUpdatesEventLoop() }

                    CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
                        ProvideAndroidCompositionLocals(owner, content)
                    }
                }
            }
        }
    }
}

我们看到在内部又调用了originalsetContent函数,其实就是调用了CompositionImplsetContent函数,内部调用composeInitial

// Composition.kt

override fun setContent(content: @Composable () -> Unit) {
    composeInitial(content)
}

private fun composeInitial(content: @Composable () -> Unit) {
    check(!disposed) { "The composition is disposed" }
    this.composable = content
    parent.composeInitial(this, composable)
}

从字面意思上看,就是对Compose进行初始化。parent就是ReComposer,可以看下再回顾一下前面的代码,CompositionContext其实就是ReComposer


// ReComposer.kt
internal override fun composeInitial(
    composition: ControlledComposition,
    content: @Composable () -> Unit
) {
    val composerWasComposing = composition.isComposing
    try {
         // 核心代码:
        composing(composition, null) {
            composition.composeContent(content)
        }
    } catch (e: Exception) {
        processCompositionError(e, composition, recoverable = true)
        return
    }

    // .....
}

当执行composeInitial函数时,内部调用了composing函数,这个函数最终执行了lambda表达式中的代码,最终还是调用了CompositionImplcomposeContent函数。

override fun composeContent(content: @Composable () -> Unit) {
    // TODO: This should raise a signal to any currently running recompose calls
    // to halt and return
    guardChanges {
        synchronized(lock) {
            drainPendingModificationsForCompositionLocked()
            guardInvalidationsLocked { invalidations ->
                val observer = observer()
                @Suppress("UNCHECKED_CAST")
                observer?.onBeginComposition(
                    this,
                    invalidations.asMap() as Map<RecomposeScope, Set<Any>?>
                )
                // Composer.xx
                composer.composeContent(invalidations, content)
                observer?.onEndComposition(this)
            }
        }
    }
}

层层套,各种套娃......最终执行了ComposercomposeContent函数。

// Composer.kt

internal fun composeContent(
    invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
    content: @Composable () -> Unit
) {
    runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
    doCompose(invalidationsRequested, content)
}

// Composer.kt

private fun doCompose(
    invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
    content: (@Composable () -> Unit)?
) {
    runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
    trace("Compose:recompose") {
        
        try {
            // ......
            
            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
        } finally {
            isComposing = false
            invalidations.clear()
            if (!complete) abortRoot()
            createFreshInsertTable()
        }
    }
}

最终是执行了ComposerinvokeComposable函数,走到这里,才是真正执行到了setContent函数内部的代码逻辑。这么一看,好像setContent一点事儿也没做,倒不是一点儿没做,是跟页面的显示没有半点关系,主要做了一些初始化的任务,例如设置State状态的监听,通知Recomposer刷新页面等。

public static final void invokeComposable(@NotNull Composer composer, @NotNull Function2 composable) {
   Intrinsics.checkNotNull(composable, "null cannot be cast to non-null type kotlin.Function2<androidx.compose.runtime.Composer, kotlin.Int, kotlin.Unit>");
   Function2 realFn = (Function2)TypeIntrinsics.beforeCheckcastToFunctionOfArity(composable, 2);
   realFn.invoke(composer, 1);
}

来看下setContent中的@Composable函数是如何执行的,在这里Compose编译器插件是会将@Composable函数转化为了一个Function2函数,然后执行了这个函数。

1.5 小结

来,通过一个流程图来看下setContent的主要逻辑:

ComponentActivityComposeViewAbstractComposeViewGlobalSnapshotManagerSnapshotAndroidComposeViewWrapper_androidCompositionImplWrappedCompositionRecomposerComposeronCreatesetContent (E)createsetParentContextsetContentsetContentViewonResumeComposeView被添加到Window上onAttachedToWindowensureCompositionCreatedsetContent (E)ensureStartedregisterGlobalWriterObserver创建doSetContent创建创建setContentsetContentcomposeInitialcomposeInitialcomposeContentcomposeContentdoComposeinvokeComposableComponentActivityComposeViewAbstractComposeViewGlobalSnapshotManagerSnapshotAndroidComposeViewWrapper_androidCompositionImplWrappedCompositionRecomposerComposer

2 Compose UI显示原理

前面,我介绍了setContent的原理,其实setContent中更多起一些辅助的作用,例如注册State修改的监听,触发Recomposer的重组,以及提供执行@Composable函数的入口等,那么真正显示的逻辑其实还是在setContent内部的@Composable函数中进行的。

setContent {
    Text(text = "1122222")
}

对于Compose当中的组件,无论是容器,还是单一的View,最终都会执行Layout函数,在Layout函数中,会创建ReusableComposeNode

inline fun Layout(
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    val viewConfiguration = LocalViewConfiguration.current
    val materialized = currentComposer.materialize(modifier)
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
            set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
            set(materialized, ComposeUiNode.SetModifier)
        },
    )
}

ReusableComposeNode内部,会通过Composer执行createNode函数,进行节点的创建,也就是执行factory内部的表达式。

@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
@Composable inline fun <T : Any, reified E : Applier<*>> ReusableComposeNode(
    noinline factory: () -> T,
    update: @DisallowComposableCalls Updater<T>.() -> Unit
) {
    if (currentComposer.applier !is E) invalidApplier()
    currentComposer.startReusableNode()
    if (currentComposer.inserting) {
        currentComposer.createNode { factory() }
    } else {
        currentComposer.useNode()
    }
    Updater<T>(currentComposer).update()
    currentComposer.endNode()
}

回到上头看一下,factory中执行的是ComposeUiNode.Constructor

val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
// 这里直接new出一个LayoutNode
internal val Constructor: () -> LayoutNode = { LayoutNode() }

其实就是创建了一个LayoutNode,也就是之前我在讲LayoutModifier时提到的,如此整个知识链路就串起来了。

所以在setContent内部的全部UI元素,都被一一转换为了LayoutNode,并组装成LayoutNode树。如果之前不知道Compose当中是在什么时候将@Composable函数转换为LayoutNode,看了这节的知识就应该知道了。