Jetpack Compose setContent 源码分析

Jetpack Compose setContent 源码分析

从何入手

先来了解一下Compose架构的分层设计

由上至下 说明 运用
material 提供了Material Design一套风格体系,包含主题系统、样式化组件 Button、AlertDialog等等
foundation 相当于面向开发者的跟基层,包含完整的UI系统和实用布局 LazyList、Row、Column等等
animation 动画层,包含平移、渐变、缩放等等,并且提供了方便开发者的动画组件 animate**AsState、Transition、Animatable、AnimatedVisibility等等
ui ui相关的基础功能,包括自定义布局、绘制、触摸反馈等等 ComposeView、Layout、LayoutNode等等
runtime 最底层的概念模型,包括数据结构、状态处理、数据同步等等 mutableStateOf、remember等等
compiler 基于Kotlin的编译器插件 处理@Composable函数

了解完整体的分层设计,而要分析的setContent()源码是处于ui层和runtime层。

Compose版本号采用1.0.1

代码块顶部注释为类位置

先看一段Compose代码

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 入口函数
        setContent { // content 函数对象
            Text("Hello Compose")
        }
    }
}

写法很简单,所以我们的目的也很简单,就是看看setContent()函数讲Text("Hello Compose")进行怎么样的逻辑传递。为了之后的代码跟随能够更加的清晰,后续讲到入口函数对象就等同于Text("Hello Compose")

ComponentActivity.setContent()

// androidx.activity.compose.ComponentActivity
public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) {
    // 1. 通过decorView找到ContentView,再获得第一个ComposeView
    val existingComposeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? ComposeView

    // 2. 如果ComposeView为空则走初始化流程
    if (existingComposeView != null) with(existingComposeView) {
        setParentCompositionContext(parent)
        setContent(content)
    } else ComposeView(this).apply {
        // 3. 初始化ComposeView肯定为空,则进入这边
        setParentCompositionContext(parent)
        // 4. 把入口函数对象传入ComposeView
        setContent(content)
        setOwners()
        // 5. 把ComposeView设置进ContentView
        setContentView(this, DefaultActivityContentLayoutParams)
    }
}

从第一步可以了解到,原来在当前窗口的decorView中的android.R.id.content中第0个位置,会存在一个ComposeView,所以ComposeView结构图可以理解成:

ComposeView 结构图.png

难道这个ComposeView肯定跟传统的View/ViewGroup有关系吗?遇事不决就看看Compose的继承关系。


image.png

果真如此,也能验证Compose架构的分层设计中上下关系,animationfoundation的工作也是在ComposeView中进行,另外还能延伸出一个问题,那是不是Compose和传统View/ViewGroup能够互相交互并且能在同一个activity混合开发?这个目前没有研究过,就先略过了。。。

第二步的话,由于分析就是初始化状态的流程且existingComposeView肯定为空,所以直接进入到第三步,传递了parentsetParentCompositionContext方法,按照案例的入口函数,

// 入口函数
setContent(parent = null) { // content 函数对象
    Text("Hello Compose")
}

所以当前parent为空,再看看方法里具体做了哪些。

// androidx.compose.ui.platform.AbstractComposeView
fun setParentCompositionContext(parent: CompositionContext?) {
    parentContext = parent
}

只是赋值操作,目前parentContext为空

第四步发现又调用了一个相同方法名的setContent,在之后的分析还会出现一个类似的setContent,每一个方法作用都是不一样,那看看当前ComposeView.setContent(入口函数对象)做了什么事情

// androidx.compose.ui.platform.ComposeView
class ComposeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {

    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)

    @Suppress("RedundantVisibilityModifier")
    protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
        private set

    @Composable
    override fun Content() {
        content.value?.invoke()
    }

    fun setContent(content: @Composable () -> Unit) {
        // 翻译为 “应该在附加到窗口上创建合成”,做一个标记
        shouldCreateCompositionOnAttachedToWindow = true
        // 赋值操作
        this.content.value = content
        if (isAttachedToWindow) {
            // 暂时不会进入
            createComposition()
        }
    }
}

整个ComposeView的类结构

AbstractComposeView
    abstract fun Content()
    open val shouldCreateCompositionOnAttachedToWindow: Boolean
        ComposeView
            val content = mutableStateOf
            fun setContent()

这样一看理解ComposeView也很容易了,它只是做了一个预备动作,告诉AbstractComposeView有人(调用ComposeView.setContent()后)把预备状态修改成准备就绪啦(shouldCreateCompositionOnAttachedToWindow = true),并且入口函数对象也存储好了,等你来拿走处理了(Content() = 入口函数对象)。

所以前两句就是给AbstractComposeView修改和准备需要的值。

if对于初始化分析来说,根本不会进入,因为当前的ComponentActivity.setContent,还没用执行到第五步setContentView,所以isAttachedToWindow肯定为false。

那再看第五步,setContentView再熟悉不过了,添加到android.R.id.content中。之后流程居然就没了?第四步都准备就绪了,之后就没有启动逻辑了?

回想一下之前讲的ComposeView的继承关系,是View的子类,那setContentView后ComposeView被附加到Window上后,会回调方法onAttachedToWindow()。我们知道ComposeView的没有这个方法的,在父类AbstractComposeView中找到了该实现。

AbstractComposeView.onAttachedToWindow()

// androidx.compose.ui.platform.AbstractComposeView
override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    previousAttachedWindowToken = windowToken
    // ComposeView 修改后预备状态
    if (shouldCreateCompositionOnAttachedToWindow) {
        // 真正启动的地方
        ensureCompositionCreated()
    }
}

shouldCreateCompositionOnAttachedToWindow这个值Compose.setContent()已经修改为true,所以直接看ensureCompositionCreated()

// androidx.compose.ui.platform.AbstractComposeView
private fun ensureCompositionCreated() {
    // composition 初始化流程为空
    if (composition == null) {
        try {
            creatingComposition = true
            // 又来一个 setContent?
            composition = setContent(resolveParentCompositionContext()) {
                // 抽象方法
                Content()
            }
        } finally {
            creatingComposition = false
        }
    }
}

composition还不确定是做什么的,先一步一步解析这个setContent涉及的内容,看看方法的入参。

// androidx.compose.ui.platform.Wrapper_androidKt.class
internal fun ViewGroup.setContent(
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
...

parent以目前的知识点也无法对它进行分析,而content就是我们的入口函数对象。调用的Content()抽象方法,方法的实现之前讲过的,内部返回的就是ComposeView.content.value

为了了解parent为何物,就要看看resolveParentCompositionContext()方法。

resolveParentCompositionContext()

// androidx.compose.ui.platform.AbstractComposeView
private fun resolveParentCompositionContext() = parentContext
    ?: findViewTreeCompositionContext()?.also { cachedViewTreeCompositionContext = it }
    ?: cachedViewTreeCompositionContext
    ?: windowRecomposer.also { cachedViewTreeCompositionContext = it }

一大堆判空逻辑,那就挨个分析。

parentContext: 就是ComponentActivity.setContent()中第三步流程,并且还知道当前案例传递为空。

findViewTreeCompositionContext(): 再看看源码怎么寻找

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
fun View.findViewTreeCompositionContext(): CompositionContext? {
    var found: CompositionContext? = compositionContext
    if (found != null) return found
    var parent: ViewParent? = parent
    while (found == null && parent is View) {
        found = parent.compositionContext
        parent = parent.getParent()
    }
    return found
}

初始化流程默认compositionContext为空,所以找上一层 parentVew, 然而整个while也是找不到需要的compositionContext,所以findViewTreeCompositionContext也会返回null。

cachedViewTreeCompositionContext: 是跟随上一个findViewTreeCompositionContext()逻辑走的,上一个为空则cachedViewTreeCompositionContext也会返回空。

windowRecomposer: 看看源码怎么寻找

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
internal val View.windowRecomposer: Recomposer
    get() {
        check(isAttachedToWindow) {
            "Cannot locate windowRecomposer; View $this is not attached to a window"
        }
        // 扩展函数拿到 contentChild
        val rootView = contentChild
        return when (val rootParentRef = rootView.compositionContext) {
            // 初始化流程为null
            null -> WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)
            is Recomposer -> rootParentRef
            else -> error("root viewTreeParentCompositionContext is not a Recomposer")
        }
    }

private val View.contentChild: View
    get() {
        var self: View = this
        var parent: ViewParent? = self.parent
        while (parent is View) {
            // 拿到 ComposeView 后返回
            if (parent.id == android.R.id.content) return self
            self = parent
            parent = self.parent
        }
        return self
    }

contentChild: View的扩展属性,当前view的parentId等于android.R.id.content时,会返回当前view。根据之前的知识(ComposeView结构图)就可以得知,当前view肯定是初始化setContentView传入的ComposeView。

再根据当前为初始化流程,所以rootView.compositionContext肯定也是为空,会进入WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)。看到这边整个流程都是在寻找compositionContext,但是第一次进入页面发现各种方法加扩展属性寻找,都压根找不到。从方法名create***,其实就可以知道找不到我就去给你创建一个,并且这个方法还把rootView(ComposeView)传入,在还没有看createAndInstallWindowRecomposer(rootView),其实也可以猜出来,创建compositionContext是肯定的,并且还会把创建的compositionContext存储到rootView(ComposeView)其中,之后再次寻找就有缓存可寻。

WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)

删减了部分代码

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
fun interface WindowRecomposerFactory {
    fun createRecomposer(windowRootView: View): Recomposer

    companion object {
        val LifecycleAware: WindowRecomposerFactory = WindowRecomposerFactory { rootView ->
            // 4. createRecomposer() lambda调用createLifecycleAwareViewTreeRecomposer()
            rootView.createLifecycleAwareViewTreeRecomposer()
        }
    }
}

private fun View.createLifecycleAwareViewTreeRecomposer(): Recomposer {
    ... 
    // 5. 创建 Recomposer
    val recomposer = Recomposer(contextWithClock)
    ...
    // 6. 返回 Recomposer
    return recomposer
}

object WindowRecomposerPolicy {

    private val factory = AtomicReference<WindowRecomposerFactory>(
        // 2. 创建 WindowRecomposerFactory
        WindowRecomposerFactory.LifecycleAware
    )

    ...

    // rootView = ComposeView
    internal fun createAndInstallWindowRecomposer(rootView: View): Recomposer {
        // 1. factory.get() 创建 WindowRecomposerFactory
        // 3. createRecomposer(rootView) 调用 WindowRecomposerFactory.createRecomposer()
        val newRecomposer = factory.get().createRecomposer(rootView)
        // 7. compositionContext 赋值到 ComposeView Tag数组中
        rootView.compositionContext = newRecomposer
                ...
        // 8. 整个创建 compositionContext 结束
        return newRecomposer
    }
}

第一步到第三步是用工厂模式WindowRecomposerFactory来创建Recomposer。回想一下我们不是要拿CompositionContext对象的么?那这个Recomposer是何方神圣?

看一下继承关系

对于Recomposer怎么理解,官方注解如下

/**
 * The scheduler for performing recomposition and applying updates to one or more [Composition]s.
 * 用于执行重组并将更新应用到一个或多个 [Composition] 的调度程序。
 * 先了解定义,之后会分析Recomposer的内部运用
 */

所以第四步到第六步就是创建CompositionContext,并且返回赋值给对象newRecomposer。

第七步是newRecomposer赋值给rootView.compositionContext, 看看赋值过程。

// androidx.compose.ui.platform.WindowRecomposer_androidKt.class
var View.compositionContext: CompositionContext?
    get() = getTag(R.id.androidx_compose_ui_view_composition_context) as? CompositionContext
    set(value) {
        setTag(R.id.androidx_compose_ui_view_composition_context, value)
    }

就是把CompositionContext添加到ComposeView的Tag数组,在Compose ui层的体系中,发现有很多类似的写法,通过set/get Tag来存取值,且之后要分析的流程也有类似写法。

第八步整个获取CompositionContext流程终于结束了。

ViewGroup.setContent()

这个时候就要再贴一下之前的流程分析。

// androidx.compose.ui.platform.AbstractComposeView
private fun ensureCompositionCreated() {
    // composition 初始化流程为空
    if (composition == null) {
        try {
            creatingComposition = true
            // 又来一个 setContent
            composition = setContent(resolveParentCompositionContext()) {
                // 抽象方法
                Content()
            }
        } finally {
            creatingComposition = false
        }
    }
}

// androidx.compose.ui.platform.Wrapper_androidKt.class
internal fun ViewGroup.setContent(
    parent: CompositionContext, // Recomposer
    content: @Composable () -> Unit // 入口函数对象
): Composition {
    GlobalSnapshotManager.ensureStarted()
    // 获得 AndroidComposeView
    val composeView =
        if (childCount > 0) {
            getChildAt(0) as? AndroidComposeView
        } else {
            removeAllViews(); null
        } ?: AndroidComposeView(context).also { 
          // 添加到 ViewGroup(ComposeView)
          addView(it.view, DefaultLayoutParams) 
        }
    // 又来一个doSetContent
    return doSetContent(composeView, parent, content)
}

ViewGroup.setContent方法很简单,获得AndroidComposeView添加到ComposeView,然后再调用doSetContent()。

以初始化流程来看肯定会创建AndroidComposeView,AndroidComposeView的结构图就可以理解为:

AndroidComposeView 结构图.png
// // androidx.compose.ui.platform.Wrapper_androidKt.class
private fun doSetContent(
    owner: AndroidComposeView,
    parent: CompositionContext, // Recomposer 
    content: @Composable () -> Unit // 入口函数对象
): Composition {
    ...
    // 创建 Composition
    val original = Composition(UiApplier(owner.root), parent)
    // 常见 WrappedComposition 并赋值到 AndroidComposeView 的Tag组数
    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)
        }
    // 又来一个setContent
    wrapped.setContent(content)
    return wrapped
}

Composition(UiApplier(owner.root), parent): 之前分析了parent,就是通过工厂模式创建的Recomposer,那要理解Composition还需要看看UiApplier是一个什么类?

UiApplier

internal class UiApplier(
    root: LayoutNode
)

参数需要一个LayoutNode,那这个LayoutNode又是什么了?

先从目前的流程场景分析的的话,就直接查看传递的AndroidComposeView.root

// androidx.compose.ui.platform.AndroidComposeView
override val root = LayoutNode().also {
    it.measurePolicy = RootMeasurePolicy
    it.modifier = Modifier
        .then(semanticsModifier)
        .then(_focusManager.modifier)
        .then(keyInputModifier)
}

原来AndroidComposeView有这个属性对象,布局层次结构中的一个元素,用于compose UI构建,那又有一个问题了,当前是确定AndroidComposeView有这个元素,那入口函数对象也是用于compose UI构建,那它有没有了?

答案是肯定有的,查看一下Text()最底层实现源码

// androidx.compose.foundation.text.CoreTextKt.class
internal fun CoreText(
    ...
) {
    ...
    Layout(
        content = if (inlineComposables.isEmpty()) {
            {}
        } else {
            { InlineChildren(text, inlineComposables) }
        },
        modifier = modifier
            .then(controller.modifiers)
            ...
                ,
        measurePolicy = controller.measurePolicy
    )
        ...
}

// androidx.compose.ui.layout.LayoutKt.class
@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
        },
        skippableUpdate = materializerOf(modifier),
        content = content
    )
}

最终是调用了ReusableComposeNode<ComposeUiNode, Applier<Any>>,而ComposeUiNode就是LayoutNode的实现接口。

internal class LayoutNode : Measurable, Remeasurement, OwnerScope, LayoutInfo, ComposeUiNode

所以结合继承关系可以把LayoutNode结构理解成:

LayoutNode 结构图.png

对于UiApplier,就可以小结一下

UiApplier是一个视图工具类,让其他使用者能更方便的操作root: LayoutNode。内部维护了一个stack = mutableListOf<T>()代表类似上图的视图树,当视图树插入、移除、移动等等都会更新stack。

那问题又来了,UiApplier是Composition的入参,究竟是哪个对象来操作UiApplier了?

Composition

// androidx.compose.runtime.CompositionKt.class
fun Composition(
    applier: Applier<*>, // UiApplier
    parent: CompositionContext // Recomposer
): Composition =
    CompositionImpl(
        parent,
        applier
    )
internal class CompositionImpl(
    private val parent: CompositionContext, // Recomposer
    private val applier: Applier<*>, // UiApplier
    recomposeContext: CoroutineContext? = null
) : ControlledComposition {
    ... 

    // 保存所有视图组合的信息(核心数据结构)
    private val slotTable = SlotTable()
    
    ...
  
    private val composer: ComposerImpl =
        ComposerImpl(
            applier = applier,
            parentContext = parent,
            slotTable = slotTable,
            abandonSet = abandonSet,
            changes = changes,
            composition = this
        ).also {
            parent.registerComposer(it)
        }
  
    ...
}

SlotTable是一个很大的话题,类似于 Gap Buffer (间隙缓冲区) ,技术有限这边就不展开说了,官方可能觉得也很难理解,在知乎上说明了SlotTable执行模式

所以可以说Composition是一个连接器,把视图树(UiApplier)和调度程序(Recomposer)连接到一起,当监听到数据(比如mutableState)变化或者添加视图等等,Recomposer会通过Composition通知SlotTable更新视图组合信息、UiApplier更新视图树等等,在分析UiApplier的Text()底层实现源码中,最终是创建了ReusableComposeNode,那在看看对应的源码。

@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()
    }
    currentComposer.disableReusing()
    Updater<T>(currentComposer).update()
    currentComposer.enableReusing()
    currentComposer.endNode()
}

currentComposer就是Composition中的composer

调用startReusableNode()就是操作SlotReader,而SlotReader其实就是SlotTable的读取控制器,对应的SlotTable也有SlotWriter写入控制器。

调用createNode()就是操作UiApplier

WrappedComposition.setContent()

终于讲到WrappedComposition.setContent(),它是一个带有处理生命周期的包裹,把AndroidComposeView和Composition包裹,当AndroidComposeView被附加到ComposeView后,会添加LifecycleEventObserver,之后触发生命周期Lifecycle.Event.ON_CREATE,会先调用连接器Composition的setContent(),再执行调度程序Recomposer的composeInitial(),再调用连接器Composition的setContent() -> composeContent(),再调用连接器中的带有SlotTable的composer.composeContent(),最终执行invokeComposable()来组合我们的入口函数对象

调用链看起来很懵逼,关键是要理解Composition、composer、CompositionContext(Recomposer)、UiApplier每个对象的关系、职责和工作的传递,最后再看看相关涉及的源码。

// androidx.compose.ui.platform.WrappedComposition
private class WrappedComposition(
    val owner: AndroidComposeView,
    val original: Composition // 
) : Composition, LifecycleEventObserver {

    private var disposed = false
    private var addedToLifecycle: Lifecycle? = null
    private var lastContent: @Composable () -> Unit = {}

    override fun setContent(content: @Composable () -> Unit) {
        owner.setOnViewTreeOwnersAvailable {
            if (!disposed) {
                val lifecycle = it.lifecycleOwner.lifecycle
                lastContent = content
                if (addedToLifecycle == null) {
                    // 1. 初始化流程第一次会进入
                    addedToLifecycle = lifecycle
                    // 2. 设置生命周期监听
                    lifecycle.addObserver(this)
                } else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
                    
                    // 4. 调用连接器的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) {
                            @OptIn(InternalComposeApi::class)
                            inspectionTable.add(currentComposer.compositionData)
                            currentComposer.collectParameterInformation()
                        }

                        LaunchedEffect(owner) { owner.keyboardVisibilityEventLoop() }
                        LaunchedEffect(owner) { owner.boundsUpdatesEventLoop() }
                                                
                        // 入口函数对象
                        CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
                            ProvideAndroidCompositionLocals(owner, content)
                        }
                    }
                }
            }
        }
    }

    override fun dispose() {
        if (!disposed) {
            disposed = true
            // 清空带生命周期的包裹
            owner.view.setTag(R.id.wrapped_composition_tag, null)
            // 移除生命周期舰艇
            addedToLifecycle?.removeObserver(this)
        }
        original.dispose()
    }

    override val hasInvalidations get() = original.hasInvalidations
    override val isDisposed: Boolean get() = original.isDisposed

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            dispose()
        } else if (event == Lifecycle.Event.ON_CREATE) {
            if (!disposed) {
                // 3. 重新执行
                setContent(lastContent)
            }
        }
    }
}
Composition -> original.setContent()
// androidx.compose.runtime.CompositionImpl
override fun setContent(content: @Composable () -> Unit) {
    check(!disposed) { "The composition is disposed" }
    this.composable = content
    // parent = Recomposer = ComposeView.compositionContext
    parent.composeInitial(this, composable)
}
Recomposer -> parent.composeInitial(this, composable)
// androidx.compose.runtime.Recomposer
internal override fun composeInitial(
    composition: ControlledComposition, // Recomposer
    content: @Composable () -> Unit // 入口函数对象
) {
    val composerWasComposing = composition.isComposing
    composing(composition, null) {
        // 走到连接器
        composition.composeContent(content)
    }
    ...
}
composition.composeContent(content)
// androidx.compose.runtime.CompositionImpl
override fun composeContent(content: @Composable () -> Unit) {
    synchronized(lock) {
        drainPendingModificationsForCompositionLocked()
        // 走到内部composer
        composer.composeContent(takeInvalidations(), content)
    }
}
composer.composeContent(takeInvalidations(), content)
// androidx.compose.runtime.ComposerImpl
internal fun composeContent(
    invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
    content: @Composable () -> Unit // 入口函数对象
) {
    runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
    doCompose(invalidationsRequested, content)
}

private fun doCompose(
    invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
    content: (@Composable () -> Unit)? // 入口函数对象
) {
    runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
    trace("Compose:recompose") {
        snapshot = currentSnapshot()
        invalidationsRequested.forEach { scope, set ->
            val location = scope.anchor?.location ?: return
            invalidations.add(Invalidation(scope, location, set))
        }
        invalidations.sortBy { it.location }
        nodeIndex = 0
        var complete = false
        isComposing = true
        try {
            startRoot()
            // Ignore reads of derivedStatOf recalculations
            observeDerivedStateRecalculations(
                start = {
                    childrenComposing++
                },
                done = {
                    childrenComposing--
                },
            ) {
                if (content != null) {
                    startGroup(invocationKey, invocation)
                    // 真正处理入口函数对象的地方
                    invokeComposable(this, content)
                    endGroup()
                } else {
                    skipCurrentGroup()
                }
            }
            endRoot()
            complete = true
        } finally {
            isComposing = false
            invalidations.clear()
            providerUpdates.clear()
            if (!complete) abortRoot()
        }
    }
}

startRoot()、endGroup() 是操作SlotTable

最后再看看invokeComposable做了什么

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

原来最终把我们的入口函数对象强转成Function2,但是当要去查看Function2是做什么的时候,没办法找到类。

原因是这边是compiler(基于kotlin的编译器插件)做的事情,它把我们的入口函数进行了修改,添加了一些参数,比如Int对应的就是groupId,所以才能强转成功。拿官方的例子来说明一下就能明白了。

@Composable
fun Counter() {
 var count by remember { mutableStateOf(0) }
 Button(
   text="Count: $count",
   onPress={ count += 1 }
 )
}
fun Counter($composer: Composer, groupId: Int) {
 $composer.start(groupId)
 var count by remember { mutableStateOf(0) }
 Button(
   text="Count: $count",
   onPress={ count += 1 }
 )
 $composer.end()
}

小结

初次看setContent源码,什么流程都记不住看不懂,只知道N个setContent跳来跳去,当把CompositioncomposerCompositionContext(Recomposer)UiApplier每个对象的关系、职责搞清楚之后,再对跳转流程理清楚,就能发现具体的工作流程了。

所分析的setContent()只是Compose Ui体系中的一小部分,发现分析完后,延伸出了更多的知识点,比如SlotTable工作的流程和时间复杂度、LayoutNode和AndroidComposeView相关的测量绘制触摸反馈、mutableState刷新机制等等,还有分析过程中提出的一个问题Compose和传统View/ViewGroup互相交互并且能在同一个activity混合开发吗?

参考资料

官方 深入详解 Jetpack Compose | 实现原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容