19.3 ComposeView 中的 Composition

637 阅读4分钟

上一章分析了 Recomposer ,  resolveParentCompositionContext() 创建了 Recomposer 后作为参数调用了 setContent() 方法创建了 Composition 对象保存到了 ComposeView 中。

internal fun AbstractComposeView.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    GlobalSnapshotManager.ensureStarted()
  	//生成 AndroidComposeView 对象添加到 ComposeView 中
    val composeView =
        if (childCount > 0) {
            getChildAt(0) as? AndroidComposeView
        } else {
            removeAllViews(); null
        } ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
    return doSetContent(composeView, parent, content)
}
//生成 WrappedComposition 对象赋值给 ComposeView.composition
private fun doSetContent(
    owner: AndroidComposeView,
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    if (inspectionWanted(owner)) {
        owner.setTag(
            R.id.inspection_slot_table_set,
            Collections.newSetFromMap(WeakHashMap<CompositionData, Boolean>())
        )
        enableDebugInspectorInfo()
    }
  	//owner.root => 根 LayoutNode, [AndroidComposeView.root]
    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
}

暂时先不分析 wrapped.setContent() ,方法执行结束后 ComposeView 中:

  1. 多了一个 AndroidComposeView 类型的子 View

  2. ComposeView.composition 属性被赋值成 WrappedComposition 对象

AndroidComposeView 是真正负责显示 UI 的组件。

Composition 负责保存 @Composable content 函数解析后的所有信息 (位置、状态、运行环境、函数体本身等) 。

Composition 中保存的数据与 AndroidComposeView 中显示的 UI 是对应关系。可以理解成他们是 ComposeView中的  State - UI 。

EDBC2DA1-16F3-41C2-8BB3-2CD32980DF1B.png

AndroidComposeView 已经不陌生了,前面分析测绘和事件传递的章节都介绍过。@Composable 函数解析后 UI 相关的部分会解析成 LayoutNode 添加到 AndroidComposeView.root 中。

LayoutNode 它本身具有测量、绘制功能。添加到 AndroidComposeView.root 后会跟随 View 的测绘回调实现 UI 的显示。Compose 只需要在初始组合或重组中添加或更新 LayoutNode ,在 View 对应的回调中 LayoutNode 我测我自己,我画我自己 。

Composition

AndroidComposeView 中  UI 保存在 root 跟节点,Composition 中代表状态的信息保存在 slotTable 属性中

SlotTable

SlotTable 推荐 探索 Jetpack Compose 内核:深入 SlotTable 系统

  • @Composable 函数解析时以 group 为单位
  • SlotTable 中 slots : Array<Any?> 保存 @Composable 函数解析后的具体信息
  • SlotTable 中 groups : IntArray 保存每个 group 的信息,配合 slots 实现树形结构
  • SlotTable 中数据基于 Gap Buffer 实现
  • SlotReader 、 SlotWriter  负责对 SlotTable 读写
  • 同一时间可以有多个 reader 进行读操作,但是同一时间只能有一个 writer 进行写操作,且在对 SlotTable 进行写操作时不可以有读操作
  • SlotReader 、 SlotWriter  配合快照一起使用 一文看懂 Jetpack Compose 快照系统

Composer

Composition 中还有一个重要属性 composer 。上一章说 Recomposer 是发起初始组合和重组,具体的工作是交给 Composer 来执行的。

10467F17-2EA1-47A4-8B8D-9679DA2732DB.png

19.1 中只分析到 doCompose() , 接着看初始组合中 doCompose() 到底做了些什么。

    private fun doCompose(
        invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
        content: (@Composable () -> Unit)?
    ){
                    if (content != null) {
                        startGroup(invocationKey, invocation)
                        invokeComposable(this, content)
                        endGroup()
                    }
    }

我们忽略 group 信息维护 ,再次推荐 探索 Jetpack Compose 内核:深入 SlotTable 系统

doCompose() 方法调用 invokeComposable(this, content)

45E5E382-F09E-4D51-B72E-E4A1D7509F4B.png

最后会执行 ComposableLambda.jvm.kt 中的 invoke()

override operator fun invoke(c: Composer, changed: Int): Any? {
  	//创建 RecomposeScope 保存到 SlotTable中
        val c = c.startRestartGroup(key)
        trackRead(c)
        val dirty = changed or if (c.changed(this)) differentBits(0) else sameBits(0)
	//执行当前 Compose 函数
        val result = (_block as (c: Composer, changed: Int) -> Any?)(c, dirty)
        //将当前的 Compose 函数保存到 RecomposeScope.block 中
        c.endRestartGroup()?.updateScope(this as (Composer, Int) -> Unit)
        return result
    }

@Composable 注解的函数经过 Compose Compiler 编译后会发生如下变化

@Composable fun test(){
	//...
}

fun test($composer: Composer<*>){
    $composer.start(123)
  	//...
    $composer.end()
}
  • 函数中增加 $composer 参数,这个参会跟着函数调用依次传递到最下级 @Composable 注解的函数
  • 每个函数调用时都会在 SlotTable 中添加 group 信息

RecomposeScope

startRestartGroup 除了在 SlotTable 中添加 group 信息,还创建了 RecomposeScope 实例添加 SlotTable 中。

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

private fun addRecomposeScope() {
    if (inserting) {
        val scope = RecomposeScopeImpl(composition as CompositionImpl)
        invalidateStack.push(scope)
        updateValue(scope)
        scope.start(compositionToken)
    } else {
		//...
    }
}

我们忽略 group 信息的话,可以想象成这个样子

0D8FA396-8463-4D18-B97F-A21EBF909CF3.png

internal class RecomposeScopeImpl(
    composition: CompositionImpl?
) : ScopeUpdateScope, RecomposeScope {
    var composition: CompositionImpl? = composition
        private set
    
    private var block: ((Composer, Int) -> Unit)? = null
  
    fun compose(composer: Composer) {
        block?.invoke(composer, 1) ?: error("Invalid restart scope")
    }  
  
    override fun invalidate() {
        composition?.invalidate(this, null)
    }
	
    override fun updateScope(block: (Composer, Int) -> Unit) { this.block = block }  
  
}  

从源码可以看出 RecomposeScopeImpl 持有当前 @Composable 函数的引用 block ,同时 compose() 方法可以执行 block。

RecomposeScopeImpl 是重组的最小单元。

LayoutNode 的解析过程

@Composable 函数执行时当遇到 UI 相关部分时, 我们那 Layout 举例

@Composable inline fun Layout(
    content: @Composable @UiComposable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    val viewConfiguration = LocalViewConfiguration.current
    ReusableComposeNode<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()
      //composer.start 添加 group 信息
    currentComposer.startReusableNode()
    if (currentComposer.inserting) {
      	//factory = ComposeUiNode.Constructor
      	//调用 ComposeUiNode 构造函数创建 LayoutNode
      	//更新 group 信息
      	//使用 UiApplier 将 LayoutNode 添加到 AndroidComposeView.root 中
        currentComposer.createNode(factory)
    } else {
        currentComposer.useNode()
    }
    currentComposer.disableReusing()
     //执行 update {} 设置 measurePolicy density layoutDirection viewConfiguration
    Updater<T>(currentComposer).update()
    currentComposer.enableReusing()
    SkippableUpdater<T>(currentComposer).skippableUpdate()
    currentComposer.startReplaceableGroup(0x7ab4aae9)
    content()
    currentComposer.endReplaceableGroup()
    currentComposer.endNode()
}

本文没有深入细节,希望这样可以帮助大家对这些概念有个初步认识,再再再次推荐 探索 Jetpack Compose 内核:深入 SlotTable 系统

80E7A68B-F2FD-4B27-BB6F-8893A88A11A1.png

  • Recomposer 提供运行环境

  • Composition 记录当前 Compose 运行状态

  • AndroidComposeView 负责显示当前状态下的 UI