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

前言

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

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

1 Compose - setContent 原理

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

kotlin 复制代码
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函数如何一步一步显示在屏幕上。

kotlin 复制代码
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:

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

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

java 复制代码
@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元素。

kotlin 复制代码
<?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的内容。

kotlin 复制代码
<?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之后,想要显示内容必须调用的函数。

kotlin 复制代码
// 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

kotlin 复制代码
override fun onAttachedToWindow() {
    super.onAttachedToWindow()

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

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

1.2.1 AbstractComposeView # ensureCompositionCreated

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

kotlin 复制代码
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

kotlin 复制代码
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对象。

kotlin 复制代码
/**
 * 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函数中的两个参数,再回头看里面的实现。

kotlin 复制代码
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触发重组,来刷新页面。

kotlin 复制代码
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函数,这里是注册了一个全局的写函数观察者。

kotlin 复制代码
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函数对值进行复写。

kotlin 复制代码
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

kotlin 复制代码
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修饰,说明会有高并发的场景。

kotlin 复制代码
@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函数调用时,传入的观察者。

kotlin 复制代码
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,然后执行其回调。

kotlin 复制代码
@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函数。

kotlin 复制代码
fun sendApplyNotifications() {
    val changes = sync {
        currentGlobalSnapshot.get().modified?.isNotEmpty() == true
    }
    if (changes)
        advanceGlobalSnapshot()
}

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

kotlin 复制代码
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是什么时候初始化的呢?

kotlin 复制代码
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函数中。

kotlin 复制代码
@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函数。

kotlin 复制代码
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

kotlin 复制代码
fun Composition(
    applier: Applier<*>,
    parent: CompositionContext
): Composition =
    CompositionImpl(
        parent,
        applier
    )

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

kotlin 复制代码
// 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

kotlin 复制代码
// 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

kotlin 复制代码
// 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函数。

kotlin 复制代码
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函数。

kotlin 复制代码
// 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)
}
kotlin 复制代码
// 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刷新页面等。

java 复制代码
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的主要逻辑:

sequenceDiagram ComponentActivity->>ComponentActivity:onCreate ComponentActivity->>ComponentActivity:setContent (E) ComponentActivity->>ComposeView: create ComposeView->>ComposeView:setParentContext ComposeView->>ComposeView:setContent ComposeView-->>ComponentActivity: setContentView ComponentActivity->>ComponentActivity:onResume ComponentActivity->>AbstractComposeView:ComposeView被添加到Window上 AbstractComposeView->>AbstractComposeView: onAttachedToWindow AbstractComposeView->>AbstractComposeView: ensureCompositionCreated AbstractComposeView->>AbstractComposeView: setContent (E) AbstractComposeView->>GlobalSnapshotManager: GlobalSnapshotManager->>GlobalSnapshotManager: ensureStarted GlobalSnapshotManager->>Snapshot: Snapshot->>Snapshot: registerGlobalWriterObserver AbstractComposeView->>AndroidComposeView: 创建 AbstractComposeView->>Wrapper_android: doSetContent Wrapper_android->>CompositionImpl: 创建 Wrapper_android->>WrappedComposition: 创建 WrappedComposition->>WrappedComposition: setContent WrappedComposition->>CompositionImpl: setContent CompositionImpl->>CompositionImpl:composeInitial CompositionImpl->>Recomposer: Recomposer->>Recomposer:composeInitial Recomposer->>CompositionImpl: CompositionImpl->>CompositionImpl: composeContent CompositionImpl->>Composer: Composer->>Composer:composeContent Composer->>Composer:doCompose Composer->>Composer:invokeComposable

2 Compose UI显示原理

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

kotlin 复制代码
setContent {
    Text(text = "1122222")
}

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

kotlin 复制代码
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内部的表达式。

kotlin 复制代码
@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

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

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

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

相关推荐
大学生小郑8 小时前
Go语言八股之channel详解
面试·golang
潜龙95278 小时前
第3.2.3节 Android动态调用链路的获取
android·调用链路
追随远方9 小时前
Android平台FFmpeg音视频开发深度指南
android·ffmpeg·音视频
撰卢10 小时前
MySQL 1366 - Incorrect string value:错误
android·数据库·mysql
恋猫de小郭11 小时前
Flutter 合并 ‘dot-shorthands‘ 语法糖,Dart 开始支持交叉编译
android·flutter·ios
牛马程序小猿猴11 小时前
15.thinkphp的上传功能
android
林家凌宇11 小时前
Flutter 3.29.3 花屏问题记录
android·flutter·skia
时丶光12 小时前
Android 查看 Logcat (可纯手机方式 无需电脑)
android·logcat
血手人屠喵帕斯12 小时前
事务连接池
android·adb
恋猫de小郭13 小时前
React Native 前瞻式重大更新 Skia & WebGPU & ThreeJS,未来可期
android·javascript·flutter·react native·react.js·ios