Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

这篇具有很好参考价值的文章主要介绍了Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文是我去年首发于稀土掘金平台的文章

全文较长共1万5千字,适合有耐心❤️的人学习
有些概念不懂的可以去4.部分概念详解这个目录先稍微学习一下

Compose源码基于最新的Compose 版本:1.0.1

系统源码基于最新的Android11 版本

注意: 后续Compose版本升级之后,有可能内部代码会稍微有些不同,但是原理都是差不多的。
因文章字数上限10万字符,所以有部分代码换成了图片

1.创建一个简单示例

override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContent {
  Text("测试文本")
 }
}

2.源码分析准备工作

为了避免其他内容干扰,只在Activity里面创建一个文本内容

先看上面的示例代码用到的类

setContent(): 设置Activity的根视图,首次会调用 Activity.setContentView()
@Composable Text():显示文本并提供语义/可访问性信息的元素

//代码来自ComponentActivity.kt
//implementation 'androidx.activity:activity-compose:1.3.0-alpha08'
fun ComponentActivity.setContent(
 parent: CompositionContext? = null,
 content: @Composable () -> Unit
)

3.源码分析

3.1-示例入口

setContent {
  Text("测试文本")
}

3.2-Activity.setContent

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

我们先看看 初始化ComposeView(this)

3.3-SnapshotState和ComposeView

//代码来自ComposeView.android.kt

class ComposeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
   //使用的是 androidx.compose.runtime.SnapshotStateKt#mutableStateOf()
   private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
    @Composable
    override fun Content() {
        //请找到目录3.7,再打开子目录13.ComposeView#content里面有这段代码的分析
        content.value?.invoke()
    }
    fun setContent(content: @Composable () -> Unit) {
       this.content.value = content
       ....
    }
    ....
}

接着看一下这段代码
content = mutableStateOf(null) 执行到下面:

//代码来自SnapshotState.kt

fun <T> mutableStateOf(
    value: T,
    //处理快照变更的策略
    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)

internal expect fun <T> createSnapshotMutableState(
    value: T,
    policy: SnapshotMutationPolicy<T>
): SnapshotMutableState<T>

createSnapshotMutableState 调用后,最终执行到:

//代码来自ActualAndroid.android.kt

internal actual fun <T> createSnapshotMutableState(
    value: T,
    policy: SnapshotMutationPolicy<T>
): SnapshotMutableState<T> = ParcelableSnapshotMutableState(value, policy)

大家把目光往上移动一下

我们发现 createSnapshotMutableState() 返回类型都是:SnapshotMutableState

ParcelableSnapshotMutableState,继承 SnapshotMutableStateImpl

我们再看一下:SnapshotMutableStateImpl,继承 SnapshotMutableState

//代码来自SnapshotState.kt

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
    override var value: T
        get() = next.readable(this).value
        set(value) = next.withCurrent {
            //比较状态值是否相等,不相等则更新状态值
            if (!policy.equivalent(it.value, value)) {
                //覆盖写入状态,最终调用notifyWrite()
                next.overwritable(this, it) { this.value = value }
                //notifyWrite() 内部调用【写观察者 writeObserver,可能是null】
                //snapshot.writeObserver?.invoke(state)
            }
        }
        ....
}

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

看到这里,我相信大部分刚刚接触这方面知识的应该还是不太懂,没关系,继续往下看

刚刚上面提到【快照的写观察者】(可能为null),那么在哪儿执行到这里呢?

答案就在下面: 文章最上面我们从源码中得知:ComposeView内部有个setContent

//代码来自ComposeView.android.kt

fun setContent(content: @Composable () -> Unit) {
    this.content.value = content
    ....
}

这个时候,会执行到下面,往上翻代码可以查看更多代码:

//代码来自SnapshotState.kt

internal open class SnapshotMutableStateImpl<T>(
    value: T,
    override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
    override var value: T
        set(value) = next.withCurrent {
           ....
        }
    ....
}

🤔为什么会执行到这里? 看一下下面的伪代码顺序:

ComposeView.setContent(){....}
this.content.value = content 中的 content是成员变量
val content = mutableStateOf<(@Composable () -> Unit)?>(null)
mutableStateOf = ParcelableSnapshotMutableState()
 
ParcelableSnapshotMutableState 继承 SnapshotMutableStateImpl

当this.content.value = content执行之后,会调用到:
SnapshotMutableStateImpl类的set(value)

看到这里应该清楚是怎么回事了吧,接下来继续看:
3.2 setContent做了什么事情? 没有分析完的代码。

3.4-Activity.setContentView

我们上面分析完ComposeView.setContent,接着分析ComponentActivity.setContentView()

由于内部执行了getWindow().setContentView(),所以会触发ComposeViewonAttachedToWindow()

//代码来自ComposeView.android.kt

 override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        previousAttachedWindowToken = windowToken
        if (shouldCreateCompositionOnAttachedToWindow) {
            ensureCompositionCreated()
        }
 }

我们来看看ensureCompositionCreated()里面干了什么:

//代码来自ComposeView.android.kt

private fun ensureCompositionCreated() {
    if (composition == null) {
       try {
            //设置当前正在创建compose content,后面用于checkAddView()判断
            creatingComposition = true
            composition = setContent(resolveParentCompositionContext()) {
                Content()
            }
       } finally {
           //compose content创建完成
           creatingComposition = false
       }
    }
}

继续分析,上面代码的resolveParentCompositionContext()

//代码来自ComposeView.android.kt

private fun resolveParentCompositionContext() = parentContext
        ?: findViewTreeCompositionContext()?.also { cachedViewTreeCompositionContext = it }
        ?: cachedViewTreeCompositionContext
        ?: windowRecomposer.also { cachedViewTreeCompositionContext = it }

如果parentContext为空,则去查找组合树上下文,没有找到则使用缓存记录的组合树上下文,缓存中也没有,则延迟创建一个Recomposer并记录到缓存中。

3.5-Recomposer如何延迟创建?

//代码来自WindowRecomposer.android.kt

internal val View.windowRecomposer: Recomposer
    get() {
        val rootView = contentChild
        return when (val rootParentRef = rootView.compositionContext) {
            null -> WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)
            is Recomposer -> rootParentRef
            else -> error("root viewTreeParentCompositionContext is not a Recomposer")
        }
    }

延迟创建Recomposer的入口方法createAndInstallWindowRecomposer()

//代码来自WindowRecomposer.android.kt

internal fun createAndInstallWindowRecomposer(rootView: View): Recomposer {
        val newRecomposer = factory.get().createRecomposer(rootView)
        rootView.compositionContext = newRecomposer
        //省略了部分代码,视图分离之后,取消设置,移除Listener防止内存泄漏
        return newRecomposer
    }

我们先看上面代码的第一行:

val newRecomposer = factory.get().createRecomposer(rootView)

factory是用AtomicReference修饰的WindowRecomposerFactory

//代码来自WindowRecomposer.android.kt

private val factory = AtomicReference<WindowRecomposerFactory>(
        WindowRecomposerFactory.LifecycleAware
)
//初始化WindowRecomposerFactory
val LifecycleAware: WindowRecomposerFactory = WindowRecomposerFactory { rootView ->
    rootView.createLifecycleAwareViewTreeRecomposer()
}

继续看createLifecycleAwareViewTreeRecomposer()如何延迟创建Recomposer的,请看下方:

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

创建一个Recomposer(),在Lifecycle.Event.ON_CREATE这个Event事件中,使用协程,在协程内部执行:recomposer.runRecomposeAndApplyChanges(),我们看到UNDISPATCHED这样的启动模式就说明,需要立即在当前线程执行协程体。

runRecomposeAndApplyChanges()等待任何关联的Composer失效并重新组合,如果重新组合成功,则将它们的更改应用于其关联的组合。

runRecomposeAndApplyChanges()分析前,先看一下recompositionRunner

//代码来自Recomposer.kt
suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->
  ....
}

先看一下recompositionRunner里面做了什么:

//代码来自Recomposer.kt

private suspend fun recompositionRunner(
    block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit) {
        withContext(broadcastFrameClock) {
            ....
            val unregisterApplyObserver = Snapshot.registerApplyObserver{...}
            ...
            try {
                synchronized(stateLock) {
                    //knownCompositions在Recomposer#composeInitial里面添加的
                    //it.invalidateAll()将会触发CompositionImpl#invalidateAll
                    knownCompositions.fastForEach { it.invalidateAll() }
                }
                coroutineScope {
                    block(parentFrameClock)
                }
            } finally {
                unregisterApplyObserver.dispose()
                ...
            }
        }
    }

Snapshot.registerApplyObserver 注册快照观察者

fun registerApplyObserver(observer: (Set<Any>, Snapshot) -> Unit): ObserverHandle {
    advanceGlobalSnapshot(emptyLambda)
    ....
}
private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
    val previousGlobalSnapshot = currentGlobalSnapshot.get()
    val result = sync {
        //获取新的全局快照
        takeNewGlobalSnapshot(previousGlobalSnapshot, block)
    }
    //上一个全局快照有状态修改,则通知已经注册的观察者
    val modified = previousGlobalSnapshot.modified
    if (modified != null) {
        val observers: List<(Set<Any>, Snapshot) -> Unit> = sync { applyObservers.toMutableList() }
        observers.fastForEach { observer ->
            //此方法将执行到SnapshotStateObserver的applyObserver: (Set<Any>, Snapshot)-> Unit = { applied, _ -> ...}
            observer(modified, previousGlobalSnapshot)
        }
    }
    ....
    return result
}

上面刚刚分析完recompositionRunner,我们再回来接着分析runRecomposeAndApplyChanges()

//代码来自Recomposer.kt

suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->
        ....
        while (shouldKeepRecomposing) {
            awaitWorkAvailable()
            if (
                synchronized(stateLock) {
                    if (!hasFrameWorkLocked) {
                        recordComposerModificationsLocked()
                        !hasFrameWorkLocked
                    } else false
                }
            ) continue
            ....
        }
}

执行到awaitWorkAvailable()此时会挂起阻塞,接下来会执行了哪些?

不知道大家有没有记起来,我们上面在分析createLifecycleAwareViewTreeRecomposer()的时候,代码中有这样一段代码:

//用伪代码粗略表达一下,细节代码,可以全局搜索一下,在文章最上面

viewTreeLifecycleOwner.lifecycle.addObserver{
    Lifecycle.Event.ON_CREATE -> recomposer.runRecomposeAndApplyChanges()
    Lifecycle.Event.ON_START -> pausableClock?.resume()
    Lifecycle.Event.ON_STOP -> pausableClock?.pause()
    Lifecycle.Event.ON_DESTROY -> recomposer.cancel()
}

我们到现在只分析了Lifecycle.Event.ON_CREATE,初始化的时候就暂停了帧生成,
当触发Lifecycle.Event.ON_START的时候,恢复帧生成 pausableClock?.resume()
接下来又会触发什么操作呢?

大家可以到此处再回头回顾一下,我们在上面又只分析到: createAndInstallWindowRecomposer() 这个方法内部的第一行代码,
这里只是介绍:3.5-Recomposer如何延迟创建?

此方法内部的其他代码主要是针对后面取消操作来做的收尾工作,防止内存泄漏

到此: Recomposer已经延迟创建成功!✅

来了来了,又回到我们上面提到的 ensureCompositionCreated() 刚刚分析完里面的resolveParentCompositionContext(),并且知道了Recomposer的延迟创建,接下来接着分析ViewGroup.setContent()

3.6-ViewGroup.setContent

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

GlobalSnapshotManager.ensureStarted()里面做了什么?
GlobalSnapshotManager是一个全局快照管理

//代码来自GlobalSnapshotManager.android.kt
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调用trySend()之后,在主线程触发Snapshot.sendApplyNotifications()

fun sendApplyNotifications() {
   val changes = sync {
       currentGlobalSnapshot.get().modified?.isNotEmpty() == true
    }
   //判断当前快照是否发现修改
   if (changes) advanceGlobalSnapshot()
}

当调用链执行到 advanceGlobalSnapshot(),这里面实现了数据更新监听回调

//代码来自Snapshot.kt

private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
    //当前快照为上一个快照
    val previousGlobalSnapshot = currentGlobalSnapshot.get()
    val result = sync {
        //获取新的快照
        takeNewGlobalSnapshot(previousGlobalSnapshot, block)
    }
    //上一个快照状态是否有变更,如果有变更则通知观察者
    val modified = previousGlobalSnapshot.modified
    if (modified != null) {
        val observers: List<(Set<Any>, Snapshot) -> Unit> = sync { applyObservers.toMutableList() }
        observers.fastForEach { observer ->
            observer(modified, previousGlobalSnapshot)
        }
    }
    ....
    return result
}

我们继续分析:ViewGroup.setContent里面的代码


//代码来自Wrapper.android.kt
internal fun ViewGroup.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)
}

首次创建UI,会触发这一行代码:

AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }

我们看一下AndroidComposeView

//代码来自AndroidComposeView.android.kt

internal class AndroidComposeView(context: Context) :
    ViewGroup(context), Owner, ViewRootForTest, PositionCalculator {
    init {
         ....
         //root = LayouNode()
         root.attach(this)
    }
    
}

我们看一下root.attach(this)代码:

//代码来自AndroidComposeView.android.kt

val root = LayoutNode().also {
        //定义了布局的测量和行为
        it.measurePolicy = RootMeasurePolicy
        //合并多个修饰符Modifier
        it.modifier = Modifier
            .then(semanticsModifier)
            .then(_focusManager.modifier)
            .then(keyInputModifier)
    }

measurePolicy 获取到的measurable能够进行:测量布局

感兴趣的童鞋可以自行查看RootMeasurePolicy里面的实现

modifier 组合多个修饰符,可以修改可组合项的大小、布局、行为和外观等等功能(注意:Modififer,修饰符函数的顺序非常重要,顺序会影响最终结果。)

LayoutNode内部的modifier、measurePolicy都是继承自ComposeUiNode类中的成员变量

接着看 fun attach(owner: Owner) 方法,注意一下入参 Owner

Owner 实现与底层视图系统的连接。在 Android 上,这连接到 Android 视图,所有布局、绘制、输入和可访问性都通过它们挂钩。

//androidx.compose.ui.node.LayoutNode#attach

internal fun attach(owner: Owner) {
        ....
        owner.onAttach(this)
        _foldedChildren.forEach { child ->
            child.attach(owner)
        }
        //由 LayoutNode 调用以请求所有者新的 measurement + layout
        //符合条件的话会执行到:
        //AndroidComposeView#scheduleMeasureAndLayout(layoutNode)里面
        requestRemeasure()
        //首次parent = null
        parent?.requestRemeasure()
        //LayoutNode包装器
        //val innerLayoutNodeWrapper = InnerPlaceable(this)
        innerLayoutNodeWrapper.attach()
        forEachDelegate { it.attach() }
        onAttach?.invoke(owner)
    }

上面分析的代码都是在初始化AndroidComposeView

执行完AndroidComposeView(context) 初始化,就会执行addView()

AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }

执行完addView(),自然会触发AndroidComposeViewonAttachedToWindow()执行

//代码来自:AndroidComposeView.android.kt
override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        //遍历所有LayoutNode,标记需要重新测量
        invalidateLayoutNodeMeasurement(root)
        //遍历所有LayoutNode,标记需要重新绘制
        invalidateLayers(root)
        //观察状态提交
        snapshotObserver.startObserving()
        ....
}
//代码来自:OwnerSnapshotObserver.kt
private val observer = SnapshotStateObserver(onChangedExecutor)
internal fun startObserving() {
  observer.start()
}
//代码来自:SnapshotStateObserver.kt
fun start() {
   //观察状态提交
   applyUnsubscribe = Snapshot.registerApplyObserver(applyObserver)
}

我们继续分析ViewGroup.setContent,查看doSetContent

3.7-doSetContent

1.初始化CompositionImpl

//androidx.compose.ui.platform.Wrapper_androidKt#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(UiApplier(owner.root), parent) 内部初始化了CompositionImpl

//代码来自:Composition.kt

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

点击查看 4.1-CompositionContext 介绍

点击查看 4.6-Applier 介绍

上面的 doSetContent() 内部初始化了一个WrappedComposition

2.wrapped.setContent(content)

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

3.lifecycle.addObserver(this)

执行完lifecycle.addObserver(this)怎么又执行到第二个if分支里面去的呢?

👇👇请看下面的代码👇👇:

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

看到这里,上面疑问就解决了,那么接下来,Composition#setContent又会发生什么事情呢?

4.original.setContent

我们来看一下,上面的代码 original.setContent

//代码来自:Wrapped.android.kt
if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
    //Composition#setContent
    original.setContent {
        ....
        CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
            ProvideAndroidCompositionLocals(owner, content)
        }
}

//代码来自:Composition.kt
override fun setContent(content: @Composable () -> Unit) {
    //this.composable => 用于HotReload重新设置content
    this.composable = content
    parent.composeInitial(this, composable)
}

5.composeInitial(…)

我们接着看 parent.composeInitial(this, composable) 做了什么事情:

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

6.doCompose(…)

上面注释2处的composition.composeContent(content),内部实现如下:

//androidx.compose.runtime.ComposerImpl#doCompose
private fun doCompose(
        invalidationsRequested: IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>,
        content: (@Composable () -> Unit)?
    ) {
        //trace(..) 内联函数,block()首尾插入Trace.xxx进行函数耗时统计
        trace("Compose:recompose") {
            snapshot = currentSnapshot()
            ....
            try {
                startRoot()
                ...
            } finally {
                if (!complete) abortRoot()
            }
        }
    }

东西确实有点多,如果大家看的有点懵的话,不要急,先看下去,然后再回头看几遍。
有些方法我们紧接着就单独展开讲解了,可能看完之后,忘了上面的代码执行到哪里了,所以需要大家再上下翻看几遍,建议跟踪源码同步一起看

7.ComposerImpl.startRoot()

如果想看更多细节可以断点调试跟踪代码看数据,下面我们来看看startRoot():

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

建议大家先到4.5这个目录下面学习一下:点击查看SlotTable介绍

8.SlotWrite.startGroup(…)

我们接着,分析上面的代码 writer.startGroup(key, objectKey ?: Composer.Empty)

//androidx.compose.runtime.SlotWriter#startGroup(int, java.lang.Object, boolean, java.lang.Object)

private fun startGroup(key: Int, objectKey: Any?, isNode: Boolean, aux: Any?) {
        //writer.beginInsert()执行之后,insertCount就大于0了
        val inserting = insertCount > 0
        //更新当前组中节点数量
        nodeCountStack.push(nodeCount)
        //groupSize
        currentGroupEnd = if (inserting) {
            //可以先去看一下4.5目录下面的SlotTable,然后再来理解它里面做的事情
            insertGroups(1)
            ....
            val dataSlotsNeeded = (if (isNode) 1 else 0) +
                (if (hasObjectKey) 1 else 0) +
                (if (hasAux) 1 else 0)
            if (dataSlotsNeeded > 0) {
                //dataSlotsNeeded表示size
                //首先将Gap移动到currentSlot,然后减小gap size slots
                //如果Gap小于size,则Gap增大来容纳size slots
                insertSlots(dataSlotsNeeded, current)
                ....
            }
            ....
        } else {
            ....
        }
    }

startRoot()代码分析完,我们继续分析上面子目录6.doCompose(...)里面剩下的代码:

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

9.invokeComposable

点击查看 4.4-ComposableLambdaImpl

10.ComposerImpl.endRoot()

//androidx.compose.runtime.ComposerImpl#endRoot

private fun endRoot() {
   //startRoot()执行的方法,对应的endRoot要有相应的收尾方法与之顺序一一对应
   endGroup()
   //与parentContext.startComposing()对应
   parentContext.doneComposing()
   endGroup()
   recordEndRoot()
   //清除回收对象
   finalizeCompose()
   //关闭slot reader
   reader.close()
}

private fun recordEndRoot() {
  //true表示已经执行过startGroup了
  if (startedGroup) {
     //记录更改,保证它被应用时,写入的内容与阅读器中的当前插槽相匹配
     recordSlotTableOperation(change = endGroupInstance)
     //reset
     startedGroup = false
  }
}

11.CompositionImpl#applyChanges

子目录5.composeInitial(...)里面的注释3:composition.applyChanges(),内部实现如下:

//androidx.compose.runtime.CompositionImpl#applyChanges
override fun applyChanges() {
    synchronized(lock) {
        //管理RememberObserver
        val manager = RememberEventDispatcher(abandonSet)
        try {
            applier.onBeginChanges()
            // Apply all changes
            slotTable.write { slots ->
                val applier = applier
                //change类型:Function3<Applier, SlotWriter, kotlin.Unit>
                changes.fastForEach { change ->
                    //1️⃣:往下翻,下面会有分析
                    //调用后会执行,执行invoke方法
                    change(applier, slots, manager)
                }
                changes.clear()
            }
            //内部代码执行AndroidComposeView#clearInvalidObservations
            applier.onEndChanges()
            //触发onRemembered()和onForgotten()回调
            manager.dispatchRememberObservers()
            //触发SideEffect的block方法回调
            manager.dispatchSideEffects()
            ...
          }
        } finally {
            //移除abandoning集合,同时触发onAbandoned()回调
            manager.dispatchAbandons()
        }
        //在释放锁之前,应用更改后,任何设置的内容都将发送到 recordModificationsOf
        //Recomposer#invalidate 里面满足条件会释放锁
        //可以看一下这里的代码:Recomposer#runRecomposeConcurrentlyAndApplyChanges
        drainPendingModificationsLocked()
    }
}

我们看一下上面注释1处的代码,执行invoke之后,触发下面lambda代码:

//androidx.compose.runtime.ComposerImpl#recordInsert
private fun recordInsert(anchor: Anchor) {
    ....
    val fixups = insertFixups.toMutableList()
    insertFixups.clear()
    ....
    recordSlotEditingOperation { applier, slots, rememberManager ->
       //触发这里的代码调用
       insertTable.write { writer ->
            //fixup类型:Function3<Applier, SlotWriter, kotlin.Unit>
            fixups.fastForEach { fixup ->
               //2️⃣:这里又将触发什么执行呢?
               fixup(applier, writer, rememberManager)
            }
        }
        ....
     }
}

接着看一下注释2处,同样会执行invoke,这个时候将会触发recordFixup里面的lambda表达式被执行:

//androidx.compose.runtime.ComposerImpl#createNode
override fun <T> createNode(factory: () -> T) {
    ...
    recordFixup { applier, slots, _ ->
       //触发这里的代码调用
       val node = factory()
       slots.updateNode(groupAnchor, node)
       ...
    }
    ...
}

点击查看更详细的ComposerImpl#createNode代码说明

执行完上面的代码之后,会接着触发,下面代码的change的lambda代码执行:

//androidx.compose.runtime.ComposerImpl#apply
override fun <V, T> apply(value: V, block: T.(V) -> Unit) {
    val operation: Change = { applier, _, _ ->
        //在ComposeUiNode.SetMeasurePolicy里面给measurePolicy赋值
        (applier.current as T).block(value)
    }
    if (inserting) recordFixup(operation)
    else recordApplierOperation(operation)
}

12.CompositionLocalProvider包裹content

点击查看4.2-CompositionLocalProvider用法
我们在original.setContent{} 函数中,我们看到了CompositionLocalProvider来隐士传递数据

//androidx.compose.ui.platform.WrappedComposition#setContent
CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
   ProvideAndroidCompositionLocals(owner, content)
}

//androidx.compose.runtime.CompositionLocalKt#CompositionLocalProvider
fun CompositionLocalProvider(vararg values: ProvidedValue<*>, content: @Composable () -> Unit) {
    currentComposer.startProviders(values)
    content() //就是我们上面的:ProvideAndroidCompositionLocals(owner, content)
    currentComposer.endProviders()
}

展开看一下ProvideAndroidCompositionLocals内部代码:

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

上面的代码中使用到了by remember,它内部方法实现如下:

//androidx.compose.runtime.ComposablesKt#remember

@Composable
inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)
   
//androidx.compose.runtime.ComposerKt#cache
@ComposeCompilerApi
inline fun <T> Composer.cache(invalid: Boolean, block: () -> T): T {
    //rememberedValue() = nextSlot()
    return rememberedValue().let {
        if (invalid || it === Composer.Empty) {
            val value = block()
            //在使用了by remember之后,会把block()块中的函数设置到当前的data slot中
            //此方法会调用到:ComposerImpl#updateValue
            //我们上面分析过这个updateValue了,如果你没有印象,自己往上翻一翻
            updateRememberedValue(value)
            value
        } else it
    } as T
}

我们简单的看一下SavedStateRegistryOwner里面做的事情,更多细节方面大家自己看代码去品味品味:

public interface SavedStateRegistryOwner extends LifecycleOwner {
    SavedStateRegistry getSavedStateRegistry();
}

//androidx.activity.ComponentActivity#ComponentActivity()
public ComponentActivity(){
   ...
   getSavedStateRegistry().registerSavedStateProvider(ACTIVITY_RESULT_TAG,
                () -> {
                    Bundle outState = new Bundle();
                    mActivityResultRegistry.onSaveInstanceState(outState);
                    return outState;
                });
   ...
}

//androidx.savedstate.SavedStateRegistry#registerSavedStateProvider
@MainThread
public void registerSavedStateProvider(@NonNull String key,
            @NonNull SavedStateProvider provider) {
        SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
        ...
}

打开SavedStateProvider看到内部有一个saveState()方法返回Bundle
我们发现 SavedStateHandle实例中,维护一个SavedStateProvider匿名内部类实例,而SavedStateHandle的读写和SavedStateProvider实例的数据读取操作,都是对实际数据容器mRegular读写。

protected void onSaveInstanceState(@NonNull Bundle outState) {
    ...
    super.onSaveInstanceState(outState);
    //把数据存储到bundle中
    mSavedStateRegistryController.performSave(outState);
}

protected void onCreate(@Nullable Bundle savedInstanceState) {
    //恢复bundle中的数据
    mSavedStateRegistryController.performRestore(savedInstanceState);
    ...
}

13.ComposeView#Content

上面的ProvideCommonCompositionLocals里面的ComposableLambdaImpl执行invoke,里面触发ComposeView#Content方法

//androidx.compose.ui.platform.ComposeView#Content
@Composable
override fun Content() {
   content.value?.invoke()
}

这里的content是什么呢?
我们在文章最上面 这个目录里面:3.3-SnapshotState和ComposeView 我们已经分析过了,从SnapshotMutableStateImpl去获取value,这个value我们也提到过了,它其实就是一个ComposableLambdaImpl
执行invoke(),会去执行这个ComposableLambdaImplinvoke

如果还是不懂ComposableLambdaImpl干什么的?
请点击查看 4.4-ComposableLambdaImpl 学习一下

我们在setContent的时候传入的是@Composable Text(...),所以这个content.value也就是我们的@Composable Text(...),所以接下来我们来分析一下它里面接下来会做什么?

3.8-@Composable Text(…)

看一下Text(...)代码,它最终会调用到CoreText

//androidx.compose.foundation.text.CoreTextKt
internal fun CoreText(text: AnnotatedString,modifier: Modifier = Modifier,...) {
    ...
    Layout(
        //如果不是inline修饰的composable方法,返回一个空的lambda
        content = if (inlineComposables.isEmpty()) {
            {}
        } else {
            { InlineChildren(text, inlineComposables) }
        },
        modifier = modifier........,
        //TextController
        measurePolicy = controller.measurePolicy
    )
    ...
}

1.Layout可组合项

我们简单看一下Layout函数:

//androidx.compose.ui.layout.LayoutKt#Layout(...)
//如果需测量和布置多个可组合项,我们需要使用 Layout 可组合项。
//这个可组合项允许我们手动测量和布置子项
inline fun Layout(content: @Composable () -> Unit,
        modifier: Modifier = Modifier,
        measurePolicy: MeasurePolicy)
  {
    ...
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        //返回一个LayoutNode()
        factory = ComposeUiNode.Constructor,
        update = {
            //设置measurePolicy
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
        },
        //往下翻,子目录『5.materializerOf』有代码分析
        skippableUpdate = materializerOf(modifier),
        content = content
    )
}

2.ReusableComposeNode

继续看这里面要做什么事情?

//androidx/compose/runtime/Composables.kt
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()
    //启动一个可重用的Node
    currentComposer.startReusableNode()
    if (currentComposer.inserting) {
        //1️⃣.创建一个Node
        currentComposer.createNode(factory)
    } else {
        //调用ComposerImpl#recordDown()
        //从slot reader读取groups的index(=parent)处的节点,并push到downNodes栈中
        //在ComposerImpl#realizeDowns被调用的时候:遍历downNodes,用Applier#down进行树向下遍历
        currentComposer.useNode()
    }
    //临时禁用重用
    currentComposer.disableReusing()
    //2️⃣.回调ReusableComposeNode内部的update
    Updater<T>(currentComposer).update()
    //启用重用
    currentComposer.enableReusing()
    //3️⃣.回调:触发materializerOf(modifier)调用
    SkippableUpdater<T>(currentComposer).skippableUpdate()
    currentComposer.startReplaceableGroup(0x7ab4aae9)
    content()
    currentComposer.endReplaceableGroup()
    currentComposer.endNode()
}

3.ComposerImpl#createNode

我们来看一下『注释1』处要做什么?

//androidx.compose.runtime.ComposerImpl#createNode
override fun <T> createNode(factory: () -> T) {
        ...
        //返回栈顶index
        val insertIndex = nodeIndexStack.peek()
        //返回当前组的锚点
        val groupAnchor = writer.anchor(writer.parent)
        groupNodeCount++
        /*
         * 问题:怎么触发这里的Lambda执行?
         * 触发的位置:CompositionImpl#applyChanges方法
         * changes.fastForEach里面,执行change(applier, slots, manager)之后
         * 执行Function3的invoke之后 -> ComposerImpl#recordInsert方法里面:
         * recordSlotEditingOperation的lambda被执行,执行fixup(applier, writer, rememberManager)
         * 执行Funtion3的invoke之后-> 就会触发此处的lambda代码块执行
         */
        recordFixup { applier, slots, _ ->
            //LayouNode
            val node = factory()
            //将node设置到slot data中
            slots.updateNode(groupAnchor, node)
            val nodeApplier = applier as Applier<T>
            //在UiApplier中是空实现...
            nodeApplier.insertTopDown(insertIndex, node)
            applier.down(node)
        }
        //和上面的注释一样,触发Lambda执行的来源一致
        recordInsertUpFixup { applier, slots, _ ->
            //返回锚点处的node,slot data中存储的数据
            val nodeToInsert = slots.node(groupAnchor)
            //表示此时正在“向上”遍历树,返回集合中最新的node
            applier.up()
            val nodeApplier = applier as Applier<Any?>
            //从底部向上构建树,会触发:LayoutNode#attach方法被执行
            nodeApplier.insertBottomUp(insertIndex, nodeToInsert)
        }
    }

4.Updater.update()

我们来看一下『注释2』处的Updater<T>(currentComposer).update()它回调到这里

//androidx.compose.ui.layout.LayoutKt#Layout(...)
ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        ...
        update = {
            //measurePolicy是从TextController中获取到的,忘了的话:自己打开源码或者往上翻一下,就在当前目录的开头地方
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
            ...
        },
       ...
    )

我们看一下这个set(...)方法里面要做什么?

//androidx.compose.runtime.Updater
fun <V> set(value: V, block: T.(value: V) -> Unit) = with(composer) {
   if (inserting || rememberedValue() != value) {
       //这个我们也分析过了,见:ComposerImpl#updateValue
       //4.3目录下面有updateValue分析,不在重复分析
       updateRememberedValue(value)
       //value = measurePolicy,block = ComposeUiNode.SetMeasurePolicy
       composer.apply(value, block)
   }
}

//androidx.compose.runtime.ComposerImpl#apply
override fun <V, T> apply(value: V, block: T.(V) -> Unit) {
    val operation: Change = { applier, _, _ ->
        //在ComposeUiNode.SetMeasurePolicy里面给measurePolicy赋值
        (applier.current as T).block(value)
    }
    if (inserting) recordFixup(operation)
    else recordApplierOperation(operation)
}

5.materializerOf

我们来看一下『注释3』处回调执行的方法:materializerOf(modifier)

//androidx.compose.ui.layout.LayoutKt#materializerOf
internal fun materializerOf(
    modifier: Modifier
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
    //拆开看源码
    val materialized = currentComposer.materialize(modifier)
    update {
        //将modifier设置为materialized,触发LayouNode的成员变量modifier的set方法
        //在触发LayoutNode#Modifier.set(value)之后,内部会使用foldOut方法,
        //进行遍历从而生成 LayoutNodeWrapper链
        set(materialized, ComposeUiNode.SetModifier)
    }
}

来看看这个Composer.materialize:

fun Composer.materialize(modifier: Modifier): Modifier {
    if (modifier.all { it !is ComposedModifier }) return modifier
    //看注释写到这是个假的可组合函数
    //此处调用的作用就是为了方便下面的element.factory调用
    startReplaceableGroup(0x48ae8da7)
    //foldIn和foldOut在下面有介绍
    val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
            if (element is ComposedModifier) {
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                val composedMod = factory(Modifier, this, 0)
                //递归调用
                materialize(composedMod)
            } else element
        )
    }
    //endGroup
    endReplaceableGroup()
    return result
}

6.foldIn、foldOut函数

我们先看一下foldIn例子

//size、padding、offset内部都使用了then
//最终会调用CombinedModifier,来连接两个Modifier     
val modifier = Modifier.size(100.dp).padding(10.dp).offset()
val resultModifier = modifier.foldIn("initial") { str, element ->
    println("str:$str,element:$element")
    "$str『和』$element"
}
println("打印结果:$resultModifier")

输出的结果如下:

//调用:
str:initial,element:SizeModifier
str:initial『和』SizeModifier,element:PaddingModifier
str:initial『和』SizeModifierr『和』PaddingModifier,element:OffsetModifier

打印结果:initial『和』SizeModifier『和』PaddingModifier『和』OffsetModifier

foldIn最终遍历调用的顺序:是一个正向的链式Modifier调用,按照添加的顺序依次有序的调用。

我们把上面的foldIn例子修改一下,替换foldIn为foldOut,看看最后输出的结果:

//调用:
str:OffsetModifier,element:initial
str:PaddingModifier,element:OffsetModifier『和』initial
str:SizeModifier,element:PaddingModifier『和』OffsetModifier『和』initial

打印结果:SizeModifier『和』PaddingModifier『和』OffsetModifier『和』initial

foldOut最终遍历调用的顺序:是一个反向的链式Modifier调用,按照添加的顺序从后往前调用。

3.9-onMeasure

我们先简单看一下ComposeView#onMeasure 内部的调用的时序图如下(省略细节,更多细节大家可以去看源码,大概意思如下👇👇)

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

简单的看完上面的时序图之后,我们来看这个方法AbstractComposeView#onMeasure

//androidx.compose.ui.platform.AbstractComposeView#onMeasure
final override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int){
    ensureCompositionCreated()
    internalOnMeasure(widthMeasureSpec, heightMeasureSpec)
}

我们看下internalOnMeasure(widthMeasureSpec, heightMeasureSpec)方法:

//androidx.compose.ui.platform.AbstractComposeView#internalOnMeasure
internal open fun internalOnMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    ...
    //子view执行measure方法,内部会触发onMeasure回调(即:AndroidComposeView#onMeasure)
    child.measure(
        MeasureSpec.makeMeasureSpec(width, MeasureSpec.getMode(widthMeasureSpec)),
        MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec)),
    )
    setMeasuredDimension(
        child.measuredWidth + paddingLeft + paddingRight,
        child.measuredHeight + paddingTop + paddingBottom
    )
}

看了上面的代码:此处为什么会触发AndroidComposeView#onMeasure?
再来简单的回顾一下:

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) {
    ...
    //继承自AbstractComposeView
    ComposeView(this).apply {
        ...
        ViewGroup.setContent(...){
            ...
            AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
            ...
        }
        ...
        setContentView(this, DefaultActivityContentLayoutParams)
    }
}

1.child.measure

我们来看看这个AndroidComposeView#onMeasure

//androidx.compose.ui.platform.AndroidComposeView

override val root = LayoutNode().also {
        it.measurePolicy = RootMeasurePolicy
        it.modifier = Modifier.then(semanticsModifier)
                              .then(_focusManager.modifier)
                              .then(keyInputModifier)
    }

//初始化并传入LayoutNode
private val measureAndLayoutDelegate = MeasureAndLayoutDelegate(root)

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    trace("AndroidOwner:onMeasure") {
        ...
        measureAndLayoutDelegate.measureAndLayout()
        //设置View的宽和高
        setMeasuredDimension(root.width, root.height)
        if (_androidViewsHandler != null) {
            //主要是用于AndroidView的,Compose里面添加xml的View的时候需要AndroidView
            androidViewsHandler.measure(
               MeasureSpec.makeMeasureSpec(root.width, MeasureSpec.EXACTLY),
               MeasureSpec.makeMeasureSpec(root.height, MeasureSpec.EXACTLY)
            )
        }
    }
}

2.measureAndLayout

上面的measureAndLayout内部的实现如下:

//androidx.compose.ui.node.MeasureAndLayoutDelegate#measureAndLayout
fun measureAndLayout(): Boolean {
    ...
    //如果没有rootConstraints的话就不需要执行下面的测量
    val rootConstraints = rootConstraints ?: return false
    var rootNodeResized = false
    if (relayoutNodes.isNotEmpty()) {
        //flag
        duringMeasureLayout = true
        try {
            //在MeasureAndLayoutDelegate#requestRemeasure里面,使用了relayoutNodes添加了待测量和布局的layouNode
            //relayoutNodes(DepthSortedSet按照树深度排序)
            relayoutNodes.popEach { layoutNode ->
                if (layoutNode.isPlaced ||
                    layoutNode.canAffectParent ||
                    layoutNode.alignmentLines.required
                ) {
                    if (layoutNode.layoutState == NeedsRemeasure) {
                        //1️⃣:往下翻有代码分析
                        if (doRemeasure(layoutNode, rootConstraints)) {
                            rootNodeResized = true
                        }
                    }
                    if (layoutNode.layoutState == NeedsRelayout && layoutNode.isPlaced) {
                        if (layoutNode === root) {
                            //在父坐标的(x, y)处放置一个Placeable
                            //内部会执行到LayoutNode#layoutChildren
                            //2️⃣:往下翻有代码分析
                            layoutNode.place(0, 0)
                        } else {
                            //相同位置替换节点
                            layoutNode.replace()
                        }
                        //跟踪正在摆放位置的节点
                        onPositionedDispatcher.onNodePositioned(layoutNode)
                    }
                    ...
                    if (postponedMeasureRequests.isNotEmpty()) {
                        //延迟的测量请求列表
                        postponedMeasureRequests.fastForEach {
                            if (it.isAttached) {
                                //请求重新测量此layoutNode以及受其测量结果影响的节点
                                requestRemeasure(it)
                            }
                        }
                        //清空列表
                        postponedMeasureRequests.clear()
                    }
                }
            }
        } finally {
            duringMeasureLayout = false
        }
        ...
    }
    return rootNodeResized
}

3.doRemeasure(layoutNode,constraints)

我们来看一下上面注释1doRemeasure(...)实现

//androidx.compose.ui.node.MeasureAndLayoutDelegate#doRemeasure

private fun doRemeasure(layoutNode: LayoutNode, rootConstraints: Constraints): Boolean {
    val sizeChanged = if (layoutNode === root) {
        //如果测量大小已更改返回true
        //1️⃣:往下翻有代码分析
        layoutNode.remeasure(rootConstraints)
    } else {
        layoutNode.remeasure()
    }
    val parent = layoutNode.parent
    if (sizeChanged) {
        if (parent == null) {
            return true
        } else if (layoutNode.measuredByParent == InMeasureBlock) {
            //请求重新测量此layoutNode以及受其测量结果影响的节点
            requestRemeasure(parent)
        } else {
            require(layoutNode.measuredByParent == InLayoutBlock)
            //为这个layoutNode(即parent)和受其位置影响的节点请求【重新布局】
            requestRelayout(parent)
        }
    }
    return false
}

4.layoutNode.remeasure

我们来分析上面注释1代码:

//androidx.compose.ui.node.LayoutNode#remeasure

//这里我们看到引入一个OuterMeasurablePlaceable,我们先去看一下这个概念
internal fun remeasure(
    constraints: Constraints? = outerMeasurablePlaceable.lastConstraints
): Boolean {
    return if (constraints != null) {
        //进去看里面的细节实现
        outerMeasurablePlaceable.remeasure(constraints)
    } else {
        false
    }
}

5.OuterMeasurablePlaceable

我们在LayouNode中发现,成员变量的地方初始化了OuterMeasurablePlaceable

//androidx.compose.ui.node.LayoutNode
internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
//初始化并传入layoutNode和innerLayoutNodeWrapper
private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)

6.outerMeasurablePlaceable.remeasure

//androidx.compose.ui.node.OuterMeasurablePlaceable#remeasure
fun remeasure(constraints: Constraints): Boolean {
    //AndroidComposeView
    val owner = layoutNode.requireOwner()
    val iteration = owner.measureIteration
    val parent = layoutNode.parent
    layoutNode.canMultiMeasure = layoutNode.canMultiMeasure ||
        (parent != null && parent.canMultiMeasure)
    //防止同一个Measurable多次调用measure()
    check(measureIteration != iteration || layoutNode.canMultiMeasure) {
        "measure() may not be called multiple times on the same Measurable"
    }
    measureIteration = owner.measureIteration
    if (layoutNode.layoutState == LayoutState.NeedsRemeasure ||
        measurementConstraints != constraints
    ) {
        ...
        //Placeable测量的大小
        val outerWrapperPreviousMeasuredSize = outerWrapper.size
        //执行到:Snapshot#enter触发block回调
        owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
            //此处会进入了DelegatingLayoutNodeWrapper#measure方法里面
            //1️⃣:往下翻有分析
            outerWrapper.measure(constraints)
        }
        ...
        return sizeChanged
    }
    return false
}

注释1处为什么代码会进入委托的layoutNode包装器里面的measure方法?
我们看一下,这里的代码:

//androidx.compose.ui.node.LayoutNode
 override var modifier: Modifier = Modifier
     //只要是从文章上面一直看到这里的,应该没有人不知道这个什么时候触发的吧
     //再啰嗦一下,不懂的可以再去看一下:SkippableUpdater<T>(currentComposer).skippableUpdate()
     set(value) {
         //创建一个新的LayoutNodeWrappers链
         val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
             ...
             //返回的就是 DelegatingLayoutNodeWrapper
             val delegate = reuseLayoutNodeWrapper(mod, toWrap)
             ...
             wrapper = delegate
             ...
         }
         //InnerPlaceable
         outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
         //🐂🍺 替换了我们的layouNodeWrapper
         outerMeasurablePlaceable.outerWrapper = outerWrapper
     }

我们再继续看DelegatingLayoutNodeWrapper里面的measure方法:

//androidx.compose.ui.node.DelegatingLayoutNodeWrapper#measure

override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
    //这里后面会触发InnerPlaceable#measure
    val placeable = wrapped.measure(constraints)
    ...
    return this
}
//androidx.compose.ui.node.LayoutNodeWrapper#performingMeasure
protected inline fun performingMeasure(
        constraints: Constraints,
        block: () -> Placeable
    ): Placeable {
        measurementConstraints = constraints
        val result = block()
        //更改绘制区域大小
        layer?.resize(measuredSize)
        return result
}

我们继续来分析上面目录2.measureAndLayout() 里面layoutNode.place(0, 0)的内部实现,最终会调用到:

7.layoutChildren

//androidx.compose.ui.node.LayoutNode#layoutChildren
internal fun layoutChildren() {
        ...
        if (layoutState == NeedsRelayout) {
            layoutState = LayingOut
            val owner = requireOwner()
            owner.snapshotObserver.observeLayoutSnapshotReads(this) {
                ...
                //内部实现:Placeable.PlacementScope.executeWithRtlMirroringValues(....)
                //最终会触发:LayoutNodeWrapper#placeAt
                innerLayoutNodeWrapper.measureResult.placeChildren()
                ...
            }
        }
        ...
    }

//androidx.compose.ui.node.LayoutNodeWrapper#onLayerBlockUpdated
fun onLayerBlockUpdated(layerBlock: (GraphicsLayerScope.() -> Unit)?) {
       ...
       if (isAttached && layerBlock != null) {
            if (layer == null) {
                //创建一个OwnedLayer,绘制传递的drawBlock,往下翻有详细代码分析
                //返回用于分离绘制内容的图层
                layer = layoutNode.requireOwner().createLayer(
                    this,
                    invalidateParentLayer
                ).apply {
                    //更改图层绘制区域的大小
                    resize(measuredSize)
                    //更改图层内容位置
                    move(position)
                }
                ...
            }
            ...
        } else {
            layer?.let {
                //重置标记,清除无效的观察者
                it.destroy()
                layoutNode.innerLayerWrapperIsDirty = true
                ...
            }
            layer = null
            lastLayerDrawingWasSkipped = false
        }
    }

LayoutNodeWrapper#placeAt调用之后,placeAt方法内部会执行onLayerBlockUpdated方法的调用

8.createLayer

override fun createLayer(
    drawBlock: (Canvas) -> Unit,
    invalidateParentLayer: () -> Unit
): OwnedLayer {
    //此处注释说:Android Q+以上的系统都是支持RenderNode的,大于Android M且低于Android Q系统版本的会尝试,失败会使用ViewLayer
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isRenderNodeCompatible) {
        //6.0以上系统会尝试去创建RenderNodeLayer,出错会使用ViewLayer
        try {
            //1️⃣:往下翻有代码分析
            return RenderNodeLayer(this,drawBlock,invalidateParentLayer)
        } catch (_: Throwable) {
            isRenderNodeCompatible = false
        }
    }
    //👇👇下面是只有RenderNodeLayer创建失败的时候才会执行到下面这种兼容模式👇👇
    if (viewLayersContainer == null) {
        if (!ViewLayer.hasRetrievedMethod) {
            //【仅测试】是否可以正常调用updateDisplayList(view),内部通过反射调用updateDisplayListIfDirty和mRecreateDisplayList方法
            //如果失败,ViewLayer.shouldUseDispatchDraw将被设置为true
            ViewLayer.updateDisplayList(View(context))
        }
        viewLayersContainer = if (ViewLayer.shouldUseDispatchDraw) {
            //2️⃣:往下翻有代码分析
            DrawChildContainer(context)
        } else {
            //3️⃣:往下翻有代码分析
            ViewLayerContainer(context)
        }
        //添加到AndroidComposeView里面
        addView(viewLayersContainer)
    }
    //返回一个ViewLayer
    return ViewLayer(this, viewLayersContainer!!, drawBlock, invalidateParentLayer)
}

9.RenderNodeLayer

注释1处初始化RenderNodeLayer

//androidx.compose.ui.platform.RenderNodeLayer

//注解表示:Android 6.0以上才可以使用
@RequiresApi(Build.VERSION_CODES.M)
internal class RenderNodeLayer(
    val ownerView: AndroidComposeView,
    val drawBlock: (Canvas) -> Unit,
    val invalidateParentLayer: () -> Unit
) : OwnedLayer {
     
    //RenderNodeLayer失效且还没有绘制的时候为true
    private var isDirty = false
        set(value) {.....}

    //可以使用Outline实现多种形状效果
    private val outlineResolver = OutlineResolver(ownerView.density)
    
    //不在需要这个Layer的时候,设置为true
    private var isDestroyed = false
	
    //是否启用画布的Z轴
    private var drawnWithZ = false
	
    //矩阵缓存类,避免重复调用android.graphics.Matrix.getValues,矩阵没有变化就可以重复使用,不需要读取和更新
    private val matrixCache = RenderNodeMatrixCache()

    //维护一份AndroidCanvas,CanvasHolder对外提供drawInto方法
    private val canvasHolder = CanvasHolder()
    
    //维护了原点位置
    private var transformOrigin: TransformOrigin = TransformOrigin.Center

    private val renderNode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        //使用android.graphics.RenderNode这个包下面的类
        //初始化val renderNode = RenderNode("Compose") 
        RenderNodeApi29(ownerView)
    } else {
        //使用android.view.RenderNode 这个包下面的类
        //初始化val renderNode = RenderNode.create("Compose", ownerView)
        RenderNodeApi23(ownerView)
    }.apply { 
        //表示此时是否存在内容重叠,true表示存在内容重叠
        setHasOverlappingRendering(true)
    }
    ....
    override fun updateLayerProperties(scaleX: Float,scaleY: Float,........) {
        ....
        //返回Outline数据是否变更
        val shapeChanged = outlineResolver.update(...)
        //设置轮廓裁剪
        renderNode.setOutline(outlineResolver.outline)
        val isClippingManually = renderNode.clipToOutline && outlineResolver.clipPath != null
        if (wasClippingManually != isClippingManually || (isClippingManually && shapeChanged)) {
            //通知AndroidComposeView调用invalidate
            invalidate()
        } else {
            triggerRepaint()
        }
        if (!drawnWithZ && renderNode.elevation > 0f) {
            //回调block,最终是wrappedBy?.invalidateLayer()调用
            invalidateParentLayer()
        }
        //用于下一次访问matrixCache的时候刷新,新的Matrix缓存
        matrixCache.invalidate()
    }
    ...
    override fun resize(size: IntSize) {
        val width = size.width
        val height = size.height
        //中心点
        renderNode.pivotX = transformOrigin.pivotFractionX * width
        renderNode.pivotY = transformOrigin.pivotFractionY * height
        //设置渲染节点的位置,返回true表示节点发生变更
        if (renderNode.setPosition(renderNode.left,renderNode.top,.....)) {
            outlineResolver.update(Size(width.toFloat(), height.toFloat()))
            renderNode.setOutline(outlineResolver.outline)
            invalidate()
            matrixCache.invalidate()
        }
    }
    ...
    override fun invalidate() {
        if (!isDirty && !isDestroyed) {
            //通知AndroidComposeView调用invalidate
            ownerView.invalidate()
            isDirty = true
        }
    }

    private fun triggerRepaint() {
        // onDescendantInvalidated is only supported on O+
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            WrapperRenderNodeLayerHelperMethods.onDescendantInvalidated(ownerView)
        } else {
            ownerView.invalidate()
        }
    }

    //LayoutNodeWrapper#draw 里面OwnedLayer不为空的话,会调用drawLayer
    override fun drawLayer(canvas: Canvas) {
        //从AndroidCanvas里面获取的internalCanvas
        val androidCanvas = canvas.nativeCanvas
        //true:画布启用硬件加速
        if (androidCanvas.isHardwareAccelerated) {
            //更新画布上的图形,满足内部条件会调用clipPath
            updateDisplayList()
            //是否支持Z轴投影
            drawnWithZ = renderNode.elevation > 0f
            if (drawnWithZ) {
                //允许渲染带有提升或平移渲染节点的投影。
                canvas.enableZ()
            }
            //内部最终会调用到一个native方法
            //native void nDrawRenderNode(long renderer, long renderNode)
            //下面有这个native代码的分析
            renderNode.drawInto(androidCanvas)
            if (drawnWithZ) {
                //禁用Z轴支持
                canvas.disableZ()
            }
        } else {
            //会执行到这个方法LayoutNodeWrapper#invoke(canvas)
            drawBlock(canvas)
            isDirty = false
        }
    }

    override fun updateDisplayList() {
        if (isDirty || !renderNode.hasDisplayList) {
            isDirty = false
            val clipPath = if (renderNode.clipToOutline) outlineResolver.clipPath else null
            //更新画布上的图形,满足内部条件会调用clipPath
            //里面的drawBlock(canvas),会执行到这个方法LayoutNodeWrapper#invoke(canvas)
            renderNode.record(canvasHolder, clipPath, drawBlock)
        }
    }

    override fun destroy() {
        isDestroyed = true
        isDirty = false
        //清除无效的观察者
        ownerView.requestClearInvalidObservations()
    }
    ...
}

10.nDrawRenderNode

我们分析一下上面renderNode.drawInto(androidCanvas) 里面的native方法

我们是怎么找到源码位置的?
1.文件肯定是android/graphics/包下面的
2.nDrawRenderNode方法是在RecordingCanvas里面
3.注释中有Critical JNI
4.肯定要找jni目录下面的文件
5.系统负责硬件加速的模块主要是HWUI

点击查看DisplayListCanvas.cpp源码

//platform/frameworks/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp

//大家可以去【DisplayListCanvas.cpp源码】对比【RecordingCanvas】的native方法包括注释都是一一对应的,我们找的没有问题就是这个文件
static JNINativeMethod gMethods[] = {
    // ------------ @FastNative ------------------
    { "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V",
            (void*) android_view_DisplayListCanvas_callDrawGLFunction },
    // ------------ @CriticalNative --------------
    { "nCreateDisplayListCanvas", "(JII)J",     (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
    ...
    //我们要找的就是它:nDrawRenderNode
    { "nDrawRenderNode",          "(JJ)V",      (void*) android_view_DisplayListCanvas_drawRenderNode },
    ...
};

// ---------------- @CriticalNative -------------------------
static jlong android_view_DisplayListCanvas_createDisplayListCanvas(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
        jint width, jint height) {
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    //这个canvas是RecordingCanvas吗?不是的,原本以为它是RecordingCanvas
    //后来发现不是,因为没有在RecordingCanvas.cpp里面找到drawRenderNode,哈哈哈哈,挺尴尬的
    //此时我就想找一下这个Canvas是什么,然后我们发现头文件用的是这个:#include <hwui/Canvas.h>
    return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode));
}
...

//nDrawRenderNode对应的就是这个方法
static void android_view_DisplayListCanvas_drawRenderNode(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong renderNodePtr) {
    //看下面的代码分析,最终我们知道‘它’原来是SkiaRecordingCanvas
    Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    canvas->drawRenderNode(renderNode);
}

我们找一下这个头文件#include <hwui/Canvas.h>
点击查看hwui/hwui/Canvas.cpp源码

//platform/frameworks/libs/hwui/hwui/Canvas.cpp

//原来是在这里创建了一个SkiaRecordingCanvas
Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
    return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}

原来如此,这一下我们就知道上面android_view_DisplayListCanvas_drawRenderNode里面初始化的Canvas原来是SkiaRecordingCanvas

点击查看SkiRecordingCanvas.cpp源码

我们简简单单的看一下这里面做了什么:

//platform/frameworks/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp

void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
    //记录子节点。当mChildNodes双端队列被清除时,Drawable dtor 将被调用。
    //mDisplayList => SkiaDisplayList
    //std::deque<RenderNodeDrawable> mChildNodes;
    mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
    auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
        renderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex());
    }
    drawDrawable(&renderNodeDrawable);
    if (renderNode->stagingProperties().isProjectionReceiver()) {
        //mProjectionReceiver指向作为投影接收器的子节点(存储在mChildNodes中)
        mDisplayList->mProjectionReceiver = &renderNodeDrawable;
    }
}

drawRenderNode对DisplayList做合并和优化

11.DrawChildContainer

目录8.createLayer,里面的:注释2处返回DrawChildContainer

internal open class DrawChildContainer(context: Context) : ViewGroup(context) {
    init {
        //表示子视图可以超出边界
        clipChildren = false
        //这里确实有点秀我一脸,还可以这样操作:inspector中隐藏此视图及子视图
        setTag(R.id.hide_in_inspector_tag, true)
    }
    //不操作子视图的摆放
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    }
    //不测量子视图
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        setMeasuredDimension(0, 0)
    }
    override fun dispatchDraw(canvas: android.graphics.Canvas) {
        var doDispatch = false
        for (i in 0 until childCount) {
            val child = getChildAt(i) as ViewLayer
            if (child.isInvalidated) {
                doDispatch = true
                break
            }
        }
        //存在无效图层的情况下,需要调用:super.dispatchDraw,否则就是空实现
        if (doDispatch) {
            super.dispatchDraw(canvas)
        }
    }
    //对外提供drawChild方法,绘制子视图,具体细节大家可以看View#drawChild内部实现
    internal fun drawChild(canvas: Canvas, view: View, drawingTime: Long) {
        super.drawChild(canvas.nativeCanvas, view, drawingTime)
    }
}

12.ViewLayerContainer

目录8.createLayer,里面的:注释3处返回ViewLayerContainer

internal class ViewLayerContainer(context: Context) : DrawChildContainer(context) {
    //空实现
    override fun dispatchDraw(canvas: android.graphics.Canvas) {
    }
    //不调用父类的实现,自己控制子视图,我们不希望视图系统强制更新displayList
    protected fun dispatchGetDisplayList() {
    }
}

3.10-onLayout

我们来看这个方法AbstractComposeView#onLayout

//androidx.compose.ui.platform.AbstractComposeView#onLayout
final override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) =
        internalOnLayout(changed, left, top, right, bottom)

internal open fun internalOnLayout(changed: Boolean,left: Int,top: Int,right: Int,bottom: Int) {
    //子View就是AndroidComposeView
    getChildAt(0)?.layout(
       paddingLeft,paddingTop,
       right - left - paddingRight,
       bottom - top - paddingBottom
    )
}

我们看一下AndroidComposeViewonLayout

//androidx.compose.ui.platform.AndroidComposeView#onLayout

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    onMeasureConstraints = null
    updatePositionCacheAndDispatch()
    if (_androidViewsHandler != null) {
       androidViewsHandler.layout(0, 0, r - l, b - t)
    }
}

3.11-dispatchDraw

我们来看这个方法AndroidComposeView#dispatchDraw

//androidx.compose.ui.platform.AndroidComposeView#dispatchDraw

override fun dispatchDraw(canvas: android.graphics.Canvas) {
    if (!isAttachedToWindow) {//视图还没有添加到窗口
        //遍历整个LayoutNode,并将所有层标记为需要重新绘制
        invalidateLayers(root)
    }
    measureAndLayout()
    isDrawingContent = true
    
    //我们文章最开头传入了一个@Composable Text(...)
    //我们从代码分析得出:drawBlock(canvas) -> 会触发LayoutNodeWrapper#invoke方法的调用
    //LayoutNodeWrapper#invoke最终执行里面的performDraw,大概调用顺序如下:
    //ModifiedDrawNode#performDraw -> DrawBackgroundModifier#draw里面的block代码块onDraw() ...-> Layout#draw 最终会在这里调用drawText来绘制文本内容
    //执行完DrawBackgroundModifier#draw里面的block代码块之后,会执行下面的drawContent() -> LayoutNodeDrawScope#drawContent -> LayoutNodeWrapper#draw
    //最终都要往这个canvas上画图
    canvasHolder.drawInto(canvas) { root.draw(this) }
    if (dirtyLayers.isNotEmpty()) {
       //重新绘制dirtyLayers
       ...
    }
    //只有ViewLayer#updateDisplayList(view)测试方法调用失败,这个变量才会为true
    if (ViewLayer.shouldUseDispatchDraw) {
        val saveCount = canvas.save()
        //裁剪画布,只显示裁剪区域,区域之外的都不显示,可想而知:没有内容能显示
        canvas.clipRect(0f, 0f, 0f, 0f)
        super.dispatchDraw(canvas)
        canvas.restoreToCount(saveCount)
    }
    dirtyLayers.clear()
    isDrawingContent = false
    if (postponedDirtyLayers != null) {
       //需要延迟重新绘制的dirtyLayers
       val postponed = postponedDirtyLayers!!
       dirtyLayers.addAll(postponed)
       postponed.clear()
    }
}

我们简单看一下,dispatchDraw内部执行了如下流程(细节太多😂,省略大部分细节):

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

4.部分概念详解

在看代码学习的时候,发现有些概念,当时看的不是很明白,深入代码仔细阅读了一些代码之后才明白的,所以这里也给大家整理一下,但是大家在学习新东西的时候,不太明白的概念可以先跳过,尝试去看下面的代码,有些代码不懂,多看几遍之后就可能明白是怎么回事了

4.1-CompositionContext

组合上下文,我们看看这个抽象类里面提供的方法:

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

4.2-CompositionLocalProvider

我们先看一眼CompositionLocalProvider方法:

//代码来自:CompositionLocal.kt
fun CompositionLocalProvider(vararg values: ProvidedValue<*>, content: @Composable () -> Unit) {
    currentComposer.startProviders(values)
    content()
    currentComposer.endProviders()
}

用法类似下面这种的方式:

CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    ...
}

LocalContentAlpha 通过 infix fun provides(value: T) 中缀providers,将ContentAlpha.medium值提供给LocalContentAlpha
我们可以通过LocalContentAlpha.current获取数据。

1.自定义CompositionLocal

CompositionLocal是一种通过 Composition 隐式传递数据的工具。

我们看一下上面提到的LocalContentAlpha是个什么?

val LocalContentAlpha = compositionLocalOf { 1f }

fun <T> compositionLocalOf(
    //快照的变更策略
    policy: SnapshotMutationPolicy<T> =
        structuralEqualityPolicy(),
    defaultFactory: () -> T
): ProvidableCompositionLocal<T> = DynamicProvidableCompositionLocal(policy, defaultFactory)

internal class DynamicProvidableCompositionLocal<T> constructor(
    private val policy: SnapshotMutationPolicy<T>,
    defaultFactory: () -> T
) : ProvidableCompositionLocal<T>(defaultFactory) {
    //此处的mutableStateOf大家自己打开源码查看一下
    //androidx.compose.runtime.SnapshotMutableStateImpl
    //如果是新值会触发:next.overwritable(this, it) { this.value = value }
    //执行get()的时候会拿新的值
    @Composable
    override fun provided(value: T): State<T> = remember { mutableStateOf(value, policy) }.apply {
        this.value = value //最新的值
    }
}

那么我们开始自己定义一个CompositionLocal

data class LayoutData(val elevation: Dp = 0.dp,val background:Color = Color.White)
val LocalLayoutData = compositionLocalOf { LayoutData() }

//用法如下:
val layoutData = LayoutData(elevation = 10.dp,background = Color.Blue)
CompositionLocalProvider(LocalLayoutData  provides layoutData) {
    val currentLayoutData:LayoutData = LocalLayoutData.current
    ....        
}

2.compositionLocalOf和staticCompositionLocalOf区别

staticCompositionLocalOf: 用于存储很少更改状态的变量数据。它的任何更改都会重新组合的整个树。
compositionLocalOf: 用于存储可以更改状态的变量数据。它的任何更改只会导致相应的可组合函数进行重新组合。

我们来看一下测试效果:

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)
compositionLocalOf 效果

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)
staticCompositionLocalOf 效果

示例代码如下:

//xxxTest.kt
val ColorLocalStatic = staticCompositionLocalOf {
    Color.Black
}

val ColorLocalDynamic = compositionLocalOf {
    Color.Black
}

@Composable
fun InnerBox(color: Color,boxValue:Int,
          content: @Composable BoxScope.() -> Unit) {
    Column (
        Modifier
            .fillMaxWidth()
            .height(150.dp)
            .background(color)) {
        Text("BoxValue:$boxValue")
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),content = content)
    }
}

@Preview
@Composable
fun TestCompositionLocalProvider(){
    var color by remember {  mutableStateOf(Color.Green) }
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        CompositionLocalProvider(ColorLocalDynamic provides color) {
            MainActivity.boxValue1++
            InnerBox(color = Color.Gray, boxValue = MainActivity.boxValue1) {
                MainActivity.boxValue2++
                InnerBox(color = ColorLocalDynamic.current, boxValue = MainActivity.boxValue2) {
                    MainActivity.boxValue3++
                    InnerBox(color = Color.Yellow, boxValue = MainActivity.boxValue3) {
                    }
                }
            }
        }
        Button(onClick = {
            color = if(color == Color.Green){
                Color.Red
            }else{
                Color.Green
            }
        },modifier = Modifier.padding(10.dp)) {
            Text(text = "测试一下: compositionLocalOf")
        }
    }
}

mBoxValue1、mBoxValue2、mBoxValue3三个静态变量

//MainActivity.kt
companion object{
    var boxValue1 = 0
    var boxValue2 = 0
    var boxValue3 = 0
}

有了上面的经验,我们来看看下面这个问题

3.为什么能用current获取值?

我们看到CompositionLocalProvider入参ProvidedValue<*>

//androidx.compose.runtime.ProvidedValue
class ProvidedValue<T> internal constructor(
    val compositionLocal: CompositionLocal<T>,
    val value: T,
    val canOverride: Boolean
)

当执行了currentComposer.startProviders(values)之后

override fun startProviders(values: Array<out ProvidedValue<*>>) {
    //先获取当前的CompositionLocalMap
    val parentScope = currentCompositionLocalScope()
    ...
    val currentProviders = invokeComposableForResult(this) {
        compositionLocalMapOf(values, parentScope)
    }
    ...
    val providers: CompositionLocalMap
    val invalid: Boolean
    if (inserting) {
        //返回新的parentScope赋值给providers
        providers = updateProviderMapGroup(parentScope, currentProviders)
        invalid = false
        hasProvider = true
    } else {
       ...
    }
    if (invalid && !inserting) {
       //更新providerUpdates当前group位置的providers
       providerUpdates[reader.currentGroup] = providers
    }
    ...
    //把providers数据写入到group中,最终保存在SlotTable的slots数组中
    start(compositionLocalMapKey, compositionLocalMap, false, providers)
}

此时我们再来看一下current如何读取数据的

//androidx.compose.runtime.CompositionLocal
sealed class CompositionLocal<T> constructor(defaultFactory: () -> T) {
    ....
    @OptIn(InternalComposeApi::class)
    inline val current: T
        @ReadOnlyComposable
        @Composable
        get() = currentComposer.consume(this)
}

这里调用了Composer#consume

//androidx.compose.runtime.ComposerImpl
override fun <T> consume(key: CompositionLocal<T>): T =
        resolveCompositionLocal(key, currentCompositionLocalScope())
        
private fun <T> resolveCompositionLocal(
        key: CompositionLocal<T>,
        scope: CompositionLocalMap
    ): T = if (scope.contains(key)) {
        //从CompositionLocalMap中获取值
        scope.getValueOf(key)
    } else {
        key.defaultValueHolder.value
    }

重点看一下这个currentCompositionLocalScope()

//androidx.compose.runtime.ComposerImpl#currentCompositionLocalScope
private fun currentCompositionLocalScope(): CompositionLocalMap {
    if (inserting && hasProvider) {
        //slot writer返回当前group的索引
        var current = writer.parent
        while (current > 0) {
            if (writer.groupKey(current) == compositionLocalMapKey &&
                writer.groupObjectKey(current) == compositionLocalMap
            ) {
                //匹配成功,返回数据
                return writer.groupAux(current) as CompositionLocalMap
            }
            //返回索引处group的父级索引
            current = writer.parent(current)
        }
    }
    if (slotTable.groupsSize > 0) {
        var current = reader.parent
        while (current > 0) {
            if (reader.groupKey(current) == compositionLocalMapKey &&
                reader.groupObjectKey(current) == compositionLocalMap
            ) {
                //我们从startProviders方法中知道,可以通过此数组获取provider
                return providerUpdates[current]
                    ?: reader.groupAux(current) as CompositionLocalMap
            }
            //返回索引处group的父级索引
            current = reader.parent(current)
        }
    }
    //返回父级provider
    return parentProvider
}

分析完上面的代码,我们才知道CompositionLocal#currentcurrentCompositionLocalScope() 里面获取Map数据的

4.3-Composition

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

1.ControlledComposition

This is the interface used by the Recomposer to control how and when a composition is invalidated and subsequently recomposed.

看了官方的解释,我们大概知道它的作用:
它是:Recomposer用来控制composition失效并重组的【接口】

2.CompositionImpl和WrappedComposition

问题: CompositionImpl和WrappedComposition是在哪儿初始化的?

//androidx.compose.ui.platform.Wrapper_androidKt
private fun doSetContent(
    owner: AndroidComposeView,
    parent: CompositionContext,
    content: @Composable () -> Unit
): Composition {
    ...
    //初始化CompositionImpl
    val original = Composition(UiApplier(owner.root), parent)
    //初始化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
}

//androidx.compose.runtime.CompositionKt
//此处初始化了CompositionImpl
fun Composition(
    applier: Applier<*>,
    parent: CompositionContext
): Composition =
    CompositionImpl(parent,applier)

结论:原来在doSetContent方法里面就初始化好了
回到正题:我们从上面的类关系图,知道CompositionImpl实现了ControlledComposition接口,
WrappedComposition实现了Composition并且构造函数内部还传入了CompositionImpl,所以它们内部是分工合作:

//androidx.compose.runtime.CompositionImpl

internal class CompositionImpl(private val parent: CompositionContext,
         private val applier: Applier<*>,
         recomposeContext: CoroutineContext? = null) : ControlledComposition {
   ....
   //🔥slotTable不懂的,可以去4.5目录下面了解一下:4.5-SlotTable 🔥
   private val slotTable = SlotTable()
   
   //由Composer调用record添加进来的
   //record怎么调用的?请往下翻一下,在这个代码区域结束的后面有分析。
   private val changes = mutableListOf<Change>()
   
   //从slotTable中删除RecomposeScope的时候就会设置为true
   internal var pendingInvalidScopes = false
   
   //如果键对象被修改,关联的范围RecomposeScope无效
   private val observations = IdentityScopeMap<RecomposeScopeImpl>()
   
   //这个集合维护了一组已经被组合解除的重组范围
   //在下一次调用recordModificationsOf时会被忽略
   private val observationsProcessed = IdentityScopeMap<RecomposeScopeImpl>()   
   
   //失效的RecomposeScope,需要调用recompose()进行重组
   private var invalidations = IdentityArrayMap<RecomposeScopeImpl, IdentityArraySet<Any>?>()
   
   private val composer: ComposerImpl = ComposerImpl(...){
        //大家可以看源码自己前后分析一下
       //后面用于移除LocalInspectionTables添加的CompositionData
       //composer可以获取slotTable,slotTable是CompositionData子类
       parent.registerComposer(it)
   }
   ....
   override fun setContent(content: @Composable () -> Unit) {
        this.composable = content
        //初始化compose并应用更改,此处的parent就是Recomposer
        parent.composeInitial(this, composable)
   }
    
   //重组
   override fun recompose(): Boolean = synchronized(lock) {
        drainPendingModificationsForCompositionLocked()
        composer.recompose(takeInvalidations()).also { shouldDrain ->
            if (!shouldDrain) drainPendingModificationsLocked()
        }
   }
   
   //请打开源码【Recomposer.kt】查看:recompositionRunner(...)
   //里面有这样一段代码会触发此处:knownCompositions.fastForEach { it.invalidateAll() }
   //knownCompositions就是在composeInitial里面添加的
   override fun invalidateAll() {
        synchronized(lock) {
            slotTable.slots.forEach { (it as? RecomposeScopeImpl)?.invalidate() }
        }
    }
}

//androidx.compose.ui.platform.WrappedComposition
private class WrappedComposition(
    val owner: AndroidComposeView,
    val original: Composition
) : Composition, LifecycleEventObserver {
    ...
    override fun setContent(content: @Composable () -> Unit) {
        ...
        //original = CompositionImpl
        original.setContent {
            ....
            inspectionTable.add(currentComposer.compositionData)
            ....
            //🔥不懂的可以去4.2目录看:4.2-CompositionLocalProvider 🔥
            CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {
               ProvideAndroidCompositionLocals(owner, content)
           }
        }
    }
}

上面的一段代码:val changes = mutableListOf<Change>() 我们在注释中提到一个问题:Composer#record 什么时候调用的?

Composer调用了 record(change) 才会往changes列表中添加change
大家打开代码:androidx.compose.runtime.ComposerImpl#updateValue

//androidx.compose.runtime.ComposerImpl#updateValue
internal fun updateValue(value: Any?) {
    if (inserting) {
       //把value 设置当前的data slot中
       writer.update(value)
       if (value is RememberObserver) {
           //原来是这里调用了record方法
           record { _, _, rememberManager -> rememberManager.remembering(value) }
       }
    }
    ....
}

4.4-ComposableLambdaImpl

看到此处的应该是从文章上面提到:invokeComposable(this, content) 方法调用看过来的,我们来看看这个方法:

//androidx.compose.runtime.ComposerKt
internal fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {
    val realFn = composable as Function2<Composer, Int, Unit>
    realFn(composer, 1)
}

这里会执行Function2的invoke方法,composable指的就是ComposableLambdaImpl,所以会执行到下面的invoke方法:

//androidx.compose.runtime.internal.ComposableLambdaImpl
override operator fun invoke(c: Composer, changed: Int): Any? {
     //重新启动Group并调用addRecomposeScope()
     //如果RecomposeScope无效Group将被重组
     val c = c.startRestartGroup(key)
     //跟踪:找到第一个无效范围并替换它,如果没有无效范围则记录它
     trackRead(c)
     //数据有变更 = 脏数据
     val dirty = changed or if (c.changed(this)) differentBits(0) else sameBits(0)
     val result = (_block as (c: Composer, changed: Int) -> Any?)(c, dirty)
     //lambda传递给updateScope,【按需】进行范围重组
     c.endRestartGroup()?.updateScope(this as (Composer, Int) -> Unit)
     return result
}

原来ComposableLambdaImpl实现了Funtion2....Function21所有invoke方法

class ComposableLambdaImpl:ComposableLambda {...}

interface ComposableLambda:Function2,....Function21

看一下上面的Composer#startRestartGroup内部实现:

//androidx.compose.runtime.ComposerImpl#startRestartGroup
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)
        //往栈中添加scope
        //ComposerImpl#getCurrentRecomposeScope 会从栈顶获取scope
        //往上翻一翻,上面的trackRead(..)方法中就使用到了这个栈顶的scope:『composer.recomposeScope』
        invalidateStack.push(scope)
        //把scope设置到当前data slot中
        updateValue(scope)
        //更新currentToken=snapshotID并设置skipped为false表示范围没有被跳过
        scope.start(snapshot.id)
    } else {
        //CompositionImpl#invalidate触发后会把scope添加到invalidations集合中
        //addRecomposeScope的前面执行了start(...),parent=currentGroup
        //返回移除的元素Invalidation
        val invalidation = invalidations.removeLocation(reader.parent)
        //slot reader获取slots数组中currentSlot数据
        val scope = reader.next() as RecomposeScopeImpl
        //用于判断是否需要进行重组
        scope.requiresRecompose = invalidation != null
        invalidateStack.push(scope)
        scope.start(snapshot.id)
    }
}

4.5-SlotTable

SlotTablecomposition slot space的Gap Buffer实现

与Gap Buffer 相似,这是一个在连续空间中存储数据的类型,底层采用数组实现,区别于数组的地方是,它的剩余空间,称为 Gap,可以根据需要移动到 Slot Table 中的任一区域,
这种实现有助于重组过程,可以轻松插入和删除元素。

我们打开Composition.kt文件查看,CompositionImpl内部的成员变量中初始化了一个SlotTable,上面的注释写到它的作用:【存储重组所需要的组合信息】
可组合函数的创建存储在SlotTable中。

我给大家找到几个介绍 GapBuffer 的链接,建议先去学习一下:

Gap Buffer Data Structure
https://www.geeksforgeeks.org/gap-buffer-data-structure/

GapBuffer
https://en.wikipedia.org/wiki/Gap_buffer
https://blog.csdn.net/ai_xiangjuan/article/details/79254691

我们来看看 SlotTable (插槽表) 里面的部分代码:

//省略部分代码...
internal class SlotTable : CompositionData, Iterable<CompositionGroup> {
    //存储group信息的数组
    var groups = IntArray(0)
        private set
    
    //存储group插槽的数组
    var slots = Array<Any?>(0) { null }
        private set
        
    //当前活动的锚点列表
    internal var anchors: ArrayList<Anchor> = arrayListOf()
}

interface CompositionGroup : CompositionData {
    //同级中,标识group的值
    //如果使用composable key则可以是对象
    val key: Any

    //SlotTreeKt#sourceInformationContextOf()中使用了sourceInfo数据
    val sourceInfo: String?

    //如果group表示的是node则是非空,否则为空
    //上面的groups.isNode(group)可以判断是否是node
    //androidx.compose.runtime.GroupIterator#next 里面node获取的方法就说明了一切
    val node: Any?
    
    //存储在此group槽表中的数据
    //包含:更改的参数值、记忆参数传递的值、记忆返回的最后一个值
    val data: Iterable<Any?>
}

//composer在合成过程中跟踪的数据
interface CompositionData {
    
    //迭代group内部的composition data
    val compositionGroups: Iterable<CompositionGroup>
    
    //groups数组没有数据的时候为true
    val isEmpty: Boolean
}

SlotTable 可能长下面这样,我们用下划线 _ 表示Gap

H l l o _ _ _ _ _ _

我们要在H后面插入新的数据,那么就需要把Gap移动到H后面

H _ _ _ _ _ _ _ l l o

移动完成之后,我们在H后面插入我们的新值e

H e _ _ _ _ _ _ l l o

4.6-Applier

Applier 是一个接口,提供回调

//代码来自:Applier.kt

interface Applier<N> {

    val current: N // 当前处理的节点

    fun onBeginChanges() {}

    fun onEndChanges() {}

    fun down(node: N)

    fun up()

    fun insertTopDown(index: Int, instance: N) // 添加节点(自顶向下)

    fun insertBottomUp(index: Int, instance: N)// 添加节点(自底向上)

    fun remove(index: Int, count: Int) //删除节点
    
    fun move(from: Int, to: Int, count: Int) // 移动节点

    fun clear() 
}

添加节点有两种方法,示例图如下:
Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

4.7-这几种setContent分别是做什么的?

//创建ComposeView设置为Activity的根视图
ComponentActivity.setContent(){}

//设置ComposeUI,调用 createComposition时发生组合
ComposeView.setContent(){}

//这是kotlin的扩展函数,返回Composition,用ViewGroup作为前缀,函数内部可以使用this使用ViewGroup
ViewGroup.setContent(){}

//内部监听AndroidComposeView是否执行完onAttachedToWindow,内部调用invoke触发内部回调
Wrapper.setContent(){}

//内部会调用Recomposer#composeInitial 初始化compose
Composition.setContent(){}

5.总结

简单的总结一下步骤如下:
(1). ComponentActivity.setContent内部:
初始化ComposeView并调用setContentView添加到根视图,当触发ComposeView#onAttachedToWindow,会添加一个childView『AndroidComposeView』

(2). 接下来会:
初始化CompositionImpl(UiApplier(layoutNode), …),初始化WrappedComposition,CompositionImpl传入WrappedComposition,再调用WrappedComposition#setContent

(3). 当执行到CompositionImpl#setContent会触发Recomposer#composeInitial调用,
执行到doCompose(...) 内部会调用invokeComposable(this, content)经过一些列调用,
后面会执行到content.value?.invoke() => ComposableLambdaImpl#invoke
触发@Composable Text加载,Text控件内部也是用了Layout可组合项
ReusableComposeNode(...)内部会执行ComposerImpl#createNode创建一个LayoutNode并设置到slot data中

(4). 当执行到composeInitial(...)里面的CompositionImpl#applyChanges,内部会通过invoke触发ComposerImpl#createNode方法里面的UiApplier#insertBottomUp调用,从底部向上构建树,最终会触发:LayoutNode#attach

(5). 当触发AbstractComposeView#onMeasure调用之后,内部会执行child.measure(...)『child 即 AndroidComposeView』,触发AndroidComposeView#onMeasure,
调用measureAndLayoutDelegate.updateRootConstraints(...)
更新root LayoutNode约束,并把LayoutNode添加到relayoutNodes的TreeSet中;

接着measureAndLayoutDelegate.measureAndLayout()就会遍历relayoutNodes,内部仅遍历,符合如下条件的LayoutNode:
layoutNode.isPlaced || layoutNode.canAffectParent || layoutNode.alignmentLines.required

如果LayoutNode的layoutState == NeedsRemeasure会执行doMeasure内部根据条件执行LayoutNode#remeasure判断Size是否发生变化,如果LayoutNode的 measuredByParent==InMeasureBlock会执行requestRemeasure(...)请求重新测量此节点和受其影响的节点;

如果measuredByParent == InLayoutBlock会执行requestRelayout(...),请求此layoutNode和受其位置影响的节点的重新布局;

如果LayoutNode的layoutState == NeedsRelayout && layoutNode.isPlaced会执行LayoutNode#place,内部会触发LayoutNodeWrapper#placeAt,然后会回调onLayerBlockUpdated,方法内部会执行:LayoutNode#createLayer来创建OwnedLayer,当触发OwnedLayer#resize会调用RenderNodeLayer#invalidate,符合条件会触发AndroidComposeView#invalidate

measureAndLayoutDelegate.measureAndLayout()执行完,调用setMeasuredDimension(...)此时AndroidComposeView#onMeasure结束,此时再执行ComposeView#onMeasure的setMeasuredDimension(...)

(6). 执行AbstractComposeView#onLayout -> AndroidComposeView#onLayout

(7). 执行AndroidComposeView#dispatchDraw绘制,绘制LayoutNode数据的地方在这里:
canvasHolder.drawInto(canvas) { root.draw(this) }
点击查看3.11-dispatchDraw代码分析

精简版-UI创建布局绘制流程图如下:文章来源地址https://www.toymoban.com/news/detail-410234.html

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

到了这里,关于Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Jetpack Compose UI架构

    Jetpack Compose是我职业生涯中最激动人心的事。它改变了我工作和问题思考的方式,引入了易用且灵活的工具,几乎可轻松实现各种功能。 早期在生产项目中尝试了Jetpack Compose后,我迅速着迷。尽管我已有使用Compose创建UI的经验,但对新的Jetpack Compose驱动特性的组织和架构引发

    2024年02月11日
    浏览(51)
  • Jetpack Compose 深入探索系列四: Compose UI

    通过 Compose runtime 集成 UI Compose UI 是一个 Kotlin 多平台框架。它提供了通过可组合函数发出 UI 的构建块和机制。除此之外,这个库还包括 Android 和 Desktop 源代码,为 Android 和 Desktop 提供集成层。 JetBrains积极维护Desktop代码库,而Google维护Android和通用代码库。Android和Desktop源代码

    2024年02月12日
    浏览(48)
  • Jetpack Compose 不止是一个UI框架~

    Jetpack Compose是用于构建原生Android UI的现代工具包。 Jetpack Compose使用更少的代码,强大的工具和直观的Kotlin API,简化并加速了Android上的UI开发。这是Android Developers 官网对它的描述。 本文不是教你Jetpack Compose 的一些基本使用方法,而是为啥我们需要Jetpack Compose 的一些简洁,让

    2024年02月03日
    浏览(52)
  • Jetpack Compose -> 声明式UI & Modifier

    本章主要介绍下 Compose 的声明式 UI 以及初级写法; 传统UI 传统 UI 方式来声明UI 是通过 xml 来进行显示的,显示文字的方式是使用 TextView,它内部显示文字的方式有两种,一种是在 xml 中直接设置,通过下面这种方式设置 这种方式是通过初始值在 xml 中进行预设置的; 还有一

    2024年02月02日
    浏览(53)
  • Jetpack Compose UI 底部弹窗实现

    使用Compose Ui的Dialog 默认是居中显示屏幕中间。 实现思路: 1.弹窗完全自定义一个全屏弹窗; 2.显示内容显示在底部区域。 3.点击其他空白区域关闭弹窗。

    2024年02月11日
    浏览(53)
  • 一文带你了解 Jetpack Compose UI 框架

    对于类似 Button 函数的这种,提供了 onClick 函数式接口供外部设置点击监听; 对于类似 Text 函数这种,没有提供显式接口设置的,通过 Modifier 类设置点击监听; Button 函数设置点击事件 @Composable fun TextButton() { Button( onClick = { Log.d(“Andoter”, this.javaClass.name) Toast.makeText(this@Mai

    2024年04月11日
    浏览(60)
  • Android笔记(六):JetPack Compose常见的UI组件

    Text显示的文本来源可以引用res-values-strings.xml中的资源,如第一个显示文本所示。 点击按钮前: 点击按钮后: 点击第一个圆角按钮不放时,显示为按钮:true Button有两方面需要注意: (1) Buttton有一个参数interactionSource,用来监听组件状态的事件源,通过它获取组件的状态来

    2024年02月04日
    浏览(54)
  • Android全新UI框架之Jetpack Compose入门基础

    Jetpack Compose是什么 如果有跨端开发经验的同学,理解和学习compose可能没有那么大的压力。简单地说,compose可以让Android的原生开发也可以使用类似rn的jsx的语法来开发 UI界面 。以往,我们开发Android原生页面的时候,通常是在xml中画相关的UI控件,然后在activity中通过findViewB

    2024年02月21日
    浏览(45)
  • Android Jetpack Compose之UI的重组和自动刷新

    我们都知道,在传统的View中,若要改变UI,需要我们修改View的私有属性,比如要修改一个TextView的文字,我们需要通过它的setText(“xxx”)方法去修改。而Compose 则是通过重组来刷新UI。在之前的状态管理的文章中也提到过重组的概念。本章主要就是介绍Compose的重组和刷新相关

    2024年02月07日
    浏览(48)
  • 《Jetpack Compose从入门到实战》第三章 定制 UI 视图

    -ui.theme.Color.kt ui.theme.Type.kt 先将Nunito Sans字体家族放入 res/font,再根据设计稿写代码 ui.theme/Shape.kt CompositionLocal 是 Jetpack Compose 中的一种数据传递方式。它可以在组合组件之间传递可变数据,而无需通过 props 或 state 管理器来传递数据。这个特性比传统的数据传递方式更为高效

    2024年02月07日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包