0%

Compose 渲染过程分析

作者:Oz

版权声明:本文图文为博主原创,转载请注明出处。

Compose 作为一个新兴高效的 UI 框架,它使用声明式的语言来构建 UI,同时,它还使用惰性计算来优化 UI 的性能。目前在线上的业务中已经有了落地实践,例如实感骑容器、KS 等项目。正所谓知其然也要知其所以然,为了探究 Compose 的实现原理,对部分源码进行了阅读整理,这里尝试回答以下几个问题:

  • 作为一种新的 UI 框架它是如何将 Compose UI 渲染到屏幕上的?★★★★★

  • 平常在写代码时,经常用到 LaunchedEffectSideEffect 是怎么样生效的? ★

  • CompositionLocal 通过 @Composable 函数隐式向下传递数据,它又是怎么样发挥作用的?★

  • @Composable AndroidView 是怎么和 Compose 进行组合渲染的?★★

Pre-Start

进入正题前,我们先看看官方的介绍:

https://developer.android.com/jetpack/compose/phases?hl=zh-cn

与大多数其他界面工具包一样,Compose 会通过几个不同的“阶段”来渲染帧。如果我们观察一下 Android View 系统,就会发现它有 3 个主要阶段:测量、布局和绘制。Compose 和它非常相似,但开头多了一个叫做“组合”的重要阶段。

帧的 3 个阶段

Compose 有 3 个主要阶段:

  1. 组合:要显示什么样的界面。Compose 运行可组合函数并创建界面说明。

  2. 布局:要放置界面的位置。该阶段包含两个步骤:测量和放置。对于布局树中的每个节点,布局元素都会根据 2D 坐标来测量并放置自己及其所有子元素。

  3. 绘制:渲染的方式。界面元素会绘制到画布(通常是设备屏幕)中。

Compose Phases

以上,官方基本将渲染的关键流程讲出来了,但是还比较抽象。为了了解得更具体深入一些,我们有必要钻研一下 Compose 的源码。我们先来看一个实际使用的例子:

1
2
3
4
5
6
7
8
9
class TestComposeActivity: ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text(text = "Hello World!")
}
}
}

以上实际画的内容是什么并不重要,重要的是我们从 setContent 这个入口追踪一下流程中发生了什么。

Compose 环境初始化

首先,为了更快的让大家了解 setContent 这个过程发生了什么,我们可以先看整理好的流程图:

compose-create-composition.png

下面我们来通过代码流程,看下这里面都做了哪些事。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
val existingComposeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView

if (existingComposeView != null) with(existingComposeView) {
setParentCompositionContext(parent)
setContent(content)
} else ComposeView(this).apply {
// Set content and parent **before** setContentView
// to have ComposeView create the composition on attach
setParentCompositionContext(parent)
setContent(content)
// Set the view tree owners before setting the content view so that the inflation process
// and attach listeners will see them already present
setOwners()
setContentView(this, DefaultActivityContentLayoutParams)
}
}

setContent 作为 ComponentActivity 的一个扩展函数,内部其实就是在构建一个 ComposeView 然后将 composeView 通过 setContentView 函数设置在 android.R.id.content 这个 Framelayout 上。

其实这里面有两处比较关键,一个是 setParentCompositionContext,另一个就是将 Composable 函数再次通过 ComposeView.setContent 函数传递了进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class AbstractComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {
...

fun setParentCompositionContext(parent: CompositionContext?) {
parentContext = parent
}

...
}

setParentCompositionContext 函数其实非常简单,只是将 parent 参数进行了一次赋值,那这个 CompositionContext 是什么呢,我们先放一下,回头再看。继续往下看,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {

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

...

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

fun setContent(content: @Composable () -> Unit) {
shouldCreateCompositionOnAttachedToWindow = true
this.content.value = content
if (isAttachedToWindow) {
createComposition()
}
}
}

这里也不复杂,将 Composable 函数进行了赋值,在 Content() 函数被使用的时候进行 invoke 调用。不过在此处就开始逐步发现关键点了,这里进行 createComposition() 调用,这与官方提到的 组合 有点关联上了,我们继续往下看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
abstract class AbstractComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {

...

fun createComposition() {
check(parentContext != null || isAttachedToWindow) {
"createComposition requires either a parent reference or the View to be attached" +
"to a window. Attach the View or call setParentCompositionReference."
}
ensureCompositionCreated()
}

/**
* 确定当前 View 组合的父级的正确 CompositionContext。
*/
private fun resolveParentCompositionContext() = parentContext
?: findViewTreeCompositionContext()?.cacheIfAlive()
?: cachedViewTreeCompositionContext?.get()?.takeIf { it.isAlive }
?: windowRecomposer.cacheIfAlive()

private fun ensureCompositionCreated() {
if (composition == null) {
try {
creatingComposition = true
composition = setContent(resolveParentCompositionContext()) {
Content()
}
} finally {
creatingComposition = false
}
}
}

...
}

此处又出现了两个关键函数,一个是 resolveParentCompositionContext 此函数的调用链很长,但其实只做了一件事,就是确保 CompositionContext 能够被确定,如果是第一次,则 parentContext 一定是 Recomposer;另一个就是再次出现一个 setContent 函数,函数的入惨是 CompositionContextComposable 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 将 Composable 组合到 AndroidComposeView 中
* 通过提供的 parent CompositionContext,新的 Composable 函数可以在逻辑上“链接”到现有的 Composition。
*/
internal fun AbstractComposeView.setContent(
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
GlobalSnapshotManager.ensureStarted()
val composeView =
if (childCount > 0) {
getChildAt(0) as? AndroidComposeView
} else {
removeAllViews(); null
} ?: AndroidComposeView(context, parent.effectCoroutineContext).also {
addView(it.view, DefaultLayoutParams)
}
return doSetContent(composeView, parent, content)
}

private fun doSetContent(
owner: AndroidComposeView,
parent: CompositionContext,
content: @Composable () -> Unit
): Composition {
if (inspectionWanted(owner)) {
owner.setTag(
R.id.inspection_slot_table_set,
Collections.newSetFromMap(WeakHashMap<CompositionData, Boolean>())
)
enableDebugInspectorInfo()
}
// 创建 UiApplier,从而构建 Composition,以便后续构建 Compose UI
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
}

这个 AbstractComposeView.setContent 扩展函数中出现了比较密集的信息。首先就是启动了全局的快照管理(至于快照是做什么的,我们此处不展开),其次就是创建一个 AndroidComposeView 并且将此 View 添加到 ComposeView 中。

继续往下看,在 doSetContent 函数中构建了 UiApplierComposition,根据参数,其实可以看到 AndroidComposeView 中有持有一个 root LayoutNode,此时我们基本上就可以合理推测 AndroidComposeView 就是最终负责 Compose UI 渲染的 View。而 UiApplier 则负责申请在合成期间发出的基于树的操作。Composition 则是管理 Composable 组合的过程,查看 Composition 的源码可以发现其中 CompositionImpl 持有 SlotTableComposerImpl 这个则是 Composition 的关键,具体的我们等后面再分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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) {
addedToLifecycle = lifecycle
lifecycle.addObserver(this)
} else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
original.setContent {

@Suppress("UNCHECKED_CAST")
val inspectionTable =
owner.getTag(R.id.inspection_slot_table_set) as?
MutableSet<CompositionData>
?: (owner.parent as? View)?.getTag(R.id.inspection_slot_table_set)
as? MutableSet<CompositionData>
if (inspectionTable != null) {
inspectionTable.add(currentComposer.compositionData)
currentComposer.collectParameterInformation()
}

LaunchedEffect(owner) { owner.keyboardVisibilityEventLoop() }
LaunchedEffect(owner) { owner.boundsUpdatesEventLoop() }

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

接着看, doSetContent 函数将 Composition 和 AndroidComposeView 又传递进 WrappedComposition 包装类中,并在 setContent 函数中设置 owner.setOnViewTreeOwnersAvailable 的回调函数,此回调函数触发的时机是 AndroidComposeView 生命周期在 onAttachedToWindow 后。并在 Lifecycle 生命周期处于 CREATED 后,此时,original.setContent 将前面流程中设置在 ComposeView 中的 Composable 函数 Content() 包裹在 CompositionLocalProvider 中,CompositionLocalProvider 则提供了LocalConfiguration、LocalContext 等等隐性数据。

至此,Compose 的初始化流程已经结束,我们总结一下,整个流程出现了 ComposeViewAndroidComposeView 两个 ViewGroup,ComposeView 负责对 Android 平台的适配并构建 Composition 相关环境, AndroidComposeView 负责连接 LayoutNode 视图系统与 View 视图系统。这其中最重要的就是创建了 CompositionImpl 且将 AndroidComposeViewCompositionImpl 通过包装类 WrappedComposition 进行关联,从而完成了 Composition 的环境的构建,为后续流程做好了准备工作。

Compostion 解析 @Composable (Composition 流程)

上面的部分提到了在 Activity onCreate 的生命周期中通过 setContent 函数为整个 Compose 创建好了环境,那接下来我们就看看后续事怎么将 @Composable 函数进行 UI 解析的吧。类比,原生 View 的 xml 布局的视图体系,其实我们也可以大致猜测会通过某些方式将 @Composable 函数解析成视图树,那究竟是怎样做的,我们不妨来研究一下。

首先,还是先看整理的流程图:

compose-composition.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
internal class AndroidComposeView(context: Context) :
ViewGroup(context), Owner, ViewRootForTest, PositionCalculator, DefaultLifecycleObserver {

...

fun setOnViewTreeOwnersAvailable(callback: (ViewTreeOwners) -> Unit) {
val viewTreeOwners = viewTreeOwners
if (viewTreeOwners != null) {
callback(viewTreeOwners)
}
if (!isAttachedToWindow) {
onViewTreeOwnersAvailable = callback
}
}

override fun onAttachedToWindow() {
super.onAttachedToWindow()
invalidateLayoutNodeMeasurement(root)
invalidateLayers(root)
snapshotObserver.startObserving()
...
if (resetViewTreeOwner) {
this.viewTreeOwners = viewTreeOwners
onViewTreeOwnersAvailable?.invoke(viewTreeOwners)
onViewTreeOwnersAvailable = null
}
...
}
}

上面提到 WrappedComposition 在 setContent 函数中设置 owner.setOnViewTreeOwnersAvailable 的回调函数,那我们来看下回调执行的时机,其实不难发现正常的回调实际就是在 AndroidComposeView 的生命周期的 onAttachedToWindow 时,所以我们顺着这个路径继续往下看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private class WrappedComposition(
val owner: AndroidComposeView,
val original: Composition
) : Composition, LifecycleEventObserver {

...
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) {
addedToLifecycle = lifecycle
lifecycle.addObserver(this)
} else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
original.setContent {
...
}
}
}
}
}
...

override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
dispose()
} else if (event == Lifecycle.Event.ON_CREATE) {
if (!disposed) {
setContent(lastContent)
}
}
}
}

上面的代码可以看出来 setOnViewTreeOwnersAvailable 回调函数被执行时,会通过 original.setContent 完成对 @Composable 的组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
internal class CompositionImpl(
/**
* 父 composition 的 [rememberCompositionContext] 用于 sub-compositions,
* 或是 root compositions 的一个 [Recomposer] 实例。
*/
private val parent: CompositionContext,

/**
* Applier,用来更新 composition 管理的树结构
*/
private val applier: Applier<*>,

recomposeContext: CoroutineContext? = null
) : ControlledComposition, RecomposeScopeOwner {

...

/**
* [SlotTable] 用于存储重组所需的 composition 信息。
*/
internal val slotTable = SlotTable()

...

/**
* 用于创建和更新此组合管理的树的 [Composer]。
*/
private val composer: ComposerImpl =
ComposerImpl(
applier = applier,
parentContext = parent,
slotTable = slotTable,
abandonSet = abandonSet,
changes = changes,
lateChanges = lateChanges,
composition = this
).also {
parent.registerComposer(it)
}

...

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

}

此处 setContent 中用到 parent 是什么呢,如果大家还有印象的话在 Composition 初始化的过程中有个 ComposeView.resolveParentCompositionContext 的过程,在这个过程中确认初始化的是 windowRecomposer 这个对象对应就是 Recomposer。那对应的就是 RecomposercomposeInitial 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@OptIn(InternalComposeApi::class)
abstract class CompositionContext internal constructor() {

internal abstract val compoundHashKey: Int
internal abstract val collectingParameterInformation: Boolean
/**
* The [CoroutineContext] with which effects for the composition will be executed in.
**/
abstract val effectCoroutineContext: CoroutineContext
internal abstract val recomposeCoroutineContext: CoroutineContext
internal abstract fun composeInitial(
composition: ControlledComposition,
content: @Composable () -> Unit
)
internal abstract fun invalidate(composition: ControlledComposition)
internal abstract fun invalidateScope(scope: RecomposeScopeImpl)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Recomposer(
effectCoroutineContext: CoroutineContext
) : CompositionContext() {

internal override fun composeInitial(
composition: ControlledComposition,
content: @Composable () -> Unit
) {
val composerWasComposing = composition.isComposing
try {
// 1. 解析 Composable 函数 -> slotTable
composing(composition, null) {
composition.composeContent(content)
}
} catch (e: Exception) {
processCompositionError(e, composition, recoverable = true)
return
}

...

try {
// 2. 记录变更操作 nodes = change(applier, slotsWriter, rememberManager)
performInitialMovableContentInserts(composition)
} catch (e: Exception) {
processCompositionError(e, composition, recoverable = true)
return
}

try {
// 3. 应用变更,实际转化为 LayoutNode 渲染树,invokeChanges()
composition.applyChanges()
composition.applyLateChanges()
} catch (e: Exception) {
processCompositionError(e)
return
}

...
}
}

Recomposer.composeInitial 函数中执行了 composition.composeContent 这个函数就是在对 @Composable 函数做解析,将 @Composable 解析成 Slot Table 的数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
internal class ComposerImpl(
/**
* An adapter that applies changes to the tree using the Applier abstraction.
*/
override val applier: Applier<*>,

/**
* Parent of this composition; a [Recomposer] for root-level compositions.
*/
private val parentContext: CompositionContext,

/**
* The slot table to use to store composition data
*/
private val slotTable: SlotTable,

private val abandonSet: MutableSet<RememberObserver>,

private var changes: MutableList<Change>,

private var lateChanges: MutableList<Change>,

/**
* The composition that owns this composer
*/
override val composition: ControlledComposition
) : Composer {

...
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()
compositionToken = snapshot.id
providerUpdates.clear()
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()

// vv Experimental for forced
@Suppress("UNCHECKED_CAST")
val savedContent = nextSlot()
if (savedContent !== content && content != null) {
updateValue(content as Any?)
}
// ^^ Experimental for forced

// Ignore reads of derivedStateOf recalculations
observeDerivedStateRecalculations(
start = {
childrenComposing++
},
done = {
childrenComposing--
},
) {
if (content != null) {
startGroup(invocationKey, invocation)
invokeComposable(this, content)
endGroup()
} else if (
forciblyRecompose &&
savedContent != null &&
savedContent != Composer.Empty
) {
startGroup(invocationKey, invocation)
@Suppress("UNCHECKED_CAST")
invokeComposable(this, savedContent as @Composable () -> Unit)
endGroup()
} else {
skipCurrentGroup()
}
}
endRoot()
complete = true
} finally {
isComposing = false
invalidations.clear()
if (!complete) abortRoot()
}
}
}
}

@Comopsable 首次执行时,产生的 Group 以及所有的状态会以此填充到 Slot Table 中,填充时会附带一个编译时给予代码位置生成的不重复的 key,所以 Slot Table 中的记录也被称作基于代码位置的存储(Positional Memoization)。Slot Table 中的状态不能直接用来渲染,UI 的渲染依赖 Composition 中的另一棵树 - LayoutNode Tree。

Slot Table 又是一块非常复杂的内容,知识储备有限,此处不展开。

Slot Table 通过 Applier 转换成渲染树:

compose-applier.png

在将 @Composable 解析成 Slot Table 的过程中,其实我们注意到有 invokeComposable() 的调用,这里就是在执行我们业务中写的 UI 逻辑代码。

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

这里额外提一下 Function2<Composer, Int, Unit> 这个函数在代码中并无实际定义,原因是 Compose Plugin 中对 @Composable 函数进行了修改,在编译过程中注入了 $composer: Composer, $changed: Int 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// cs.android.com
class ComposerParamTransformer(
context: IrPluginContext,
symbolRemapper: DeepCopySymbolRemapper,
stabilityInferencer: StabilityInferencer,
private val decoysEnabled: Boolean,
metrics: ModuleMetrics,
) :
AbstractComposeLowering(context, symbolRemapper, metrics, stabilityInferencer),
ModuleLoweringPass {

...

private fun IrSimpleFunction.copyWithComposerParam(): IrSimpleFunction {
assert(explicitParameters.lastOrNull()?.name != KtxNameConventions.COMPOSER_PARAMETER) {
"Attempted to add composer param to $this, but it has already been added."
}
return copy().also { fn ->
val oldFn = this

// NOTE: it's important to add these here before we recurse into the body in
// order to avoid an infinite loop on circular/recursive calls
transformedFunctionSet.add(fn)
transformedFunctions[oldFn] = fn

// The overridden symbols might also be composable functions, so we want to make sure
// and transform them as well
fn.overriddenSymbols = overriddenSymbols.map {
it.owner.withComposerParamIfNeeded().symbol
}

// if we are transforming a composable property, the jvm signature of the
// corresponding getters and setters have a composer parameter. Since Kotlin uses the
// lack of a parameter to determine if it is a getter, this breaks inlining for
// composable property getters since it ends up looking for the wrong jvmSignature.
// In this case, we manually add the appropriate "@JvmName" annotation so that the
// inliner doesn't get confused.
fn.correspondingPropertySymbol?.let { propertySymbol ->
if (!fn.hasAnnotation(DescriptorUtils.JVM_NAME)) {
val propertyName = propertySymbol.owner.name.identifier
val name = if (fn.isGetter) {
JvmAbi.getterName(propertyName)
} else {
JvmAbi.setterName(propertyName)
}
fn.annotations += jvmNameAnnotation(name)
}
}

val valueParametersMapping = explicitParameters
.zip(fn.explicitParameters)
.toMap()

val currentParams = fn.valueParameters.size
val realParams = currentParams - fn.contextReceiverParametersCount

// $composer
// val COMPOSER_PARAMETER = Name.identifier("\$composer")
val composerParam = fn.addValueParameter {
name = KtxNameConventions.COMPOSER_PARAMETER
type = composerType.makeNullable()
origin = IrDeclarationOrigin.DEFINED
isAssignable = true
}

// $changed[n]
// val CHANGED_PARAMETER = Name.identifier("\$changed")
val changed = KtxNameConventions.CHANGED_PARAMETER.identifier
for (i in 0 until changedParamCount(realParams, fn.thisParamCount)) {
fn.addValueParameter(
if (i == 0) changed else "$changed$i",
context.irBuiltIns.intType
)
}

// $default[n]
if (oldFn.requiresDefaultParameter()) {
val defaults = KtxNameConventions.DEFAULT_PARAMETER.identifier
for (i in 0 until defaultParamCount(currentParams)) {
fn.addValueParameter(
if (i == 0) defaults else "$defaults$i",
context.irBuiltIns.intType,
IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION
)
}
}

inlineLambdaInfo.scan(fn)

fn.transformChildrenVoid(object : IrElementTransformerVoid() {
...
})
}
}
}

除此之外,还有很重要的一步是将 Slot Table 数据转化成可以渲染的 LayoutNode,将需要进行的变更进行 record 记录,此处仅仅是记录变更的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
internal class ComposerImpl(
/**
* An adapter that applies changes to the tree using the Applier abstraction.
*/
override val applier: Applier<*>,

/**
* Parent of this composition; a [Recomposer] for root-level compositions.
*/
private val parentContext: CompositionContext,

/**
* The slot table to use to store composition data
*/
private val slotTable: SlotTable,

private val abandonSet: MutableSet<RememberObserver>,

private var changes: MutableList<Change>,

private var lateChanges: MutableList<Change>,

/**
* The composition that owns this composer
*/
override val composition: ControlledComposition
) : Composer {

private fun insertMovableContentGuarded(
references: List<Pair<MovableContentStateReference, MovableContentStateReference?>>
) {
fun positionToParentOf(slots: SlotWriter, applier: Applier<Any?>, index: Int) {
...
}

fun currentNodeIndex(slots: SlotWriter): Int {
...
}

fun positionToInsert(slots: SlotWriter, anchor: Anchor, applier: Applier<Any?>): Int {
...
}

withChanges(lateChanges) {
record(resetSlotsInstance)
references.fastForEach { (to, from) ->
val anchor = to.anchor
val location = to.slotTable.anchorIndex(anchor)
var effectiveNodeIndex = 0
realizeUps()
// Insert content at the anchor point
record { applier, slots, _ ->
@Suppress("UNCHECKED_CAST")
applier as Applier<Any?>
effectiveNodeIndex = positionToInsert(slots, anchor, applier)
}

if (from == null) {
...
record { applier, slots, rememberManager -> ... }
...
} else {
...
record { applier, slots, rememberManager -> ... }
...
}

record { applier, slots, _ ->
@Suppress("UNCHECKED_CAST")
applier as Applier<Any?>
positionToParentOf(slots, applier, 0)
slots.endGroup()
}
writersReaderDelta = 0
}

}

最后,通过 applyChangesInLocked 将 changes 操作执行最终形成 LayoutNode Tree。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
internal class CompositionImpl(
/**
* The parent composition from [rememberCompositionContext], for sub-compositions, or the an
* instance of [Recomposer] for root compositions.
*/
private val parent: CompositionContext,

/**
* The applier to use to update the tree managed by the composition.
*/
private val applier: Applier<*>,

recomposeContext: CoroutineContext? = null
) : ControlledComposition {

...
private fun applyChangesInLocked(changes: MutableList<Change>) {
val manager = RememberEventDispatcher(abandonSet)
try {
if (changes.isEmpty()) return
trace("Compose:applyChanges") {
applier.onBeginChanges()

// Apply all changes
slotTable.write { slots ->
val applier = applier
changes.fastForEach { change ->
change(applier, slots, manager)
}
changes.clear()
}
applier.onEndChanges()
}

// Side effects run after lifecycle observers so that any remembered objects
// that implement RememberObserver receive onRemembered before a side effect
// that captured it and operates on it can run.
manager.dispatchRememberObservers()
manager.dispatchSideEffects()

...
} finally {
// Only dispatch abandons if we do not have any late changes. The instances in the
// abandon set can be remembered in the late changes.
if (this.lateChanges.isEmpty())
manager.dispatchAbandons()
}
}

...
}

在这个过程中,通过 RememberEventDispatcher 回调所有的 Effect 执行。这也就是我们写代码时 Effect 的调用时机。

AndroidComposeView 与 LayoutNode 渲染树(Layout → Drawing 流程)

前面我们了解到,通过解析 @Composable 函数,生成了一个可以真正被渲染的渲染树,那么后续就是如果将渲染树渲染到屏幕上。这里还是先看整理的流程图:

compose-layout-drawing.png

类比 Android 的经验,虽说 Compose 框架本身的机制是一套独立的渲染流程,但跑在 Android 上还是无法避免与原生 View 的体系有些桥接的过程。通过第一部分 Compose 初始化过程我们知道 Compose 最终绘制的承载 View 是 AndroidComposeView,所以我们的突破口依然还是 View 的 onMeasure onLayoutdispatchDraw 生命周期。其实 Compose 的 Layout 和 Draw 过程相比 Composition 过程算是简单很多了,同样的有 Measure 、Layout、Draw 的流程,不过官方文档中似乎一直没有提及 Measure 的过程,所以以官方的归类 Compose 的 Layout 过程是横跨了AndroidComposeView 的onMeasure onLayoutdispatchDraw 三个生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
internal class AndroidComposeView(context: Context) :
ViewGroup(context), Owner, ViewRootForTest, PositionCalculator, DefaultLifecycleObserver {

...

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
trace("AndroidOwner:onMeasure") {
if (!isAttachedToWindow) {
invalidateLayoutNodeMeasurement(root)
}
val (minWidth, maxWidth) = convertMeasureSpec(widthMeasureSpec)
val (minHeight, maxHeight) = convertMeasureSpec(heightMeasureSpec)

val constraints = Constraints(minWidth, maxWidth, minHeight, maxHeight)
if (onMeasureConstraints == null) {
// first onMeasure after last onLayout
onMeasureConstraints = constraints
wasMeasuredWithMultipleConstraints = false
} else if (onMeasureConstraints != constraints) {
// we were remeasured twice with different constraints after last onLayout
wasMeasuredWithMultipleConstraints = true
}
measureAndLayoutDelegate.updateRootConstraints(constraints)
measureAndLayoutDelegate.measureOnly()
setMeasuredDimension(root.width, root.height)
if (_androidViewsHandler != null) {
androidViewsHandler.measure(
MeasureSpec.makeMeasureSpec(root.width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(root.height, MeasureSpec.EXACTLY)
)
}
}
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
measureAndLayoutDelegate.measureAndLayout(resendMotionEventOnLayout)
onMeasureConstraints = null

updatePositionCacheAndDispatch()
if (_androidViewsHandler != null) {
//...
androidViewsHandler.layout(0, 0, r - l, b - t)
}
}

override fun dispatchDraw(canvas: android.graphics.Canvas) {
if (!isAttachedToWindow) {
invalidateLayers(root)
}
measureAndLayout()

isDrawingContent = true
// ...
canvasHolder.drawInto(canvas) { root.draw(this) }

if (dirtyLayers.isNotEmpty()) {
for (i in 0 until dirtyLayers.size) {
val layer = dirtyLayers[i]
layer.updateDisplayList()
}
}

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) {
val postponed = postponedDirtyLayers!!
dirtyLayers.addAll(postponed)
postponed.clear()
}
}
}

不知道大家有没有注意到在 AndroidComposeView 的生命周期中,有出现 AndroidViewsHandleronMeasureonLayout 中的调用,其实看源码可以发现,这就是 @Composable AndroidView 与 Compose UI 混用时的调用。同样的,AndroidView 也会在 Composition 的过程中被解析成 LayoutNode,不过原生 View 的 Measure 和 Layout 还是需要依靠 View 的基础进行的,而到的需要 draw 时,因为 LayoutNode 也同样适用了 Android Canvas,所以 draw 的过程是被当作普通节点来处理的。

核心类关系图

以上我们了解了 Compose 的渲染流程,为了对 Compose 核心类之间的关系有个更全面的了解,这里简单画了一下类图。

compose-class.png

这里其实可以看出在整个渲染过程中有两个核心的圈子,一个是以 ComposeView 为容器的 Composition 环境(包含初始化、解析 @Composable 组件);另一个是以 AndroidComposeView 为容器的渲染机制(底层绘制原理和应用层的 UI 组件)。

总结

最后,我们再来看看开头我们试图解答的几个问题,是否都有了答案:

作为一种新的 UI 框架它是如何将 Compose UI 渲染到屏幕上的?

简单回顾下以上的所有过程,可以用下图总结:

compose-full-phases

除了上面我们分析到的与 Android View 生命周期相关的部分外,还有和帧信号带来的 Recompose 过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@ExperimentalComposeUiApi
fun View.createLifecycleAwareWindowRecomposer(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
lifecycle: Lifecycle? = null
): Recomposer {
// Only access AndroidUiDispatcher.CurrentThread if we would use an element from it,
// otherwise prevent lazy initialization.
val baseContext = if (coroutineContext[ContinuationInterceptor] == null ||
coroutineContext[MonotonicFrameClock] == null
) {
AndroidUiDispatcher.CurrentThread + coroutineContext
} else coroutineContext

...

val recomposer = Recomposer(contextWithClockAndMotionScale)
val runRecomposeScope = CoroutineScope(contextWithClockAndMotionScale)
val viewTreeLifecycle = checkNotNull(lifecycle ?: ViewTreeLifecycleOwner.get(this)?.lifecycle) { ... }

// ...
addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {}
override fun onViewDetachedFromWindow(v: View) {
removeOnAttachStateChangeListener(this)
recomposer.cancel()
}
}
)
viewTreeLifecycle.addObserver(
object : LifecycleEventObserver {
override fun onStateChanged(
lifecycleOwner: LifecycleOwner,
event: Lifecycle.Event
) {
val self = this
when (event) {
Lifecycle.Event.ON_CREATE -> {
// Undispatched launch since we've configured this scope
// to be on the UI thread
runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
var durationScaleJob: Job? = null
try {
durationScaleJob = systemDurationScaleSettingConsumer?.let {
val durationScaleStateFlow = getAnimationScaleFlowFor(
context.applicationContext
)
it.scaleFactor = durationScaleStateFlow.value
launch {
durationScaleStateFlow.collect { scaleFactor ->
it.scaleFactor = scaleFactor
}
}
}
recomposer.runRecomposeAndApplyChanges()
} finally {
...
}
}
}
Lifecycle.Event.ON_START -> pausableClock?.resume()
Lifecycle.Event.ON_STOP -> pausableClock?.pause()
Lifecycle.Event.ON_DESTROY -> { recomposer.cancel() }
...
}
}
}
)
return recomposer
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class Recomposer(
effectCoroutineContext: CoroutineContext
) : CompositionContext() {

private val shouldKeepRecomposing: Boolean
get() = synchronized(stateLock) { !isClosed } ||
effectJob.children.any { it.isActive }
...

suspend fun runRecomposeAndApplyChanges() = recompositionRunner { parentFrameClock ->
val toRecompose = mutableListOf<ControlledComposition>()
val toInsert = mutableListOf<MovableContentStateReference>()
val toApply = mutableListOf<ControlledComposition>()
val toLateApply = mutableSetOf<ControlledComposition>()
val toComplete = mutableSetOf<ControlledComposition>()

fun clearRecompositionState() { ... }

fun fillToInsert() { ... }

while (shouldKeepRecomposing) {
awaitWorkAvailable()

// Don't await a new frame if we don't have frame-scoped work
if (
synchronized(stateLock) {
if (!hasFrameWorkLocked) {
recordComposerModificationsLocked()
!hasFrameWorkLocked
} else false
}
) continue

// 与下一帧对齐任务以合并更改。
parentFrameClock.withFrameNanos { frameTime ->
// Dispatch MonotonicFrameClock frames first; this may produce new
// composer invalidations that we must handle during the same frame.
if (broadcastFrameClock.hasAwaiters) {
trace("Recomposer:animation") {
// Propagate the frame time to anyone who is awaiting from the
// recomposer clock.
broadcastFrameClock.sendFrame(frameTime)

// Ensure any global changes are observed
Snapshot.sendApplyNotifications()
}
}

trace("Recomposer:recompose") {
// Drain any composer invalidations from snapshot changes and record
// composers to work on
synchronized(stateLock) {
recordComposerModificationsLocked()

compositionInvalidations.fastForEach { toRecompose += it }
compositionInvalidations.clear()
}

// Perform recomposition for any invalidated composers
val modifiedValues = IdentityArraySet<Any>()
val alreadyComposed = IdentityArraySet<ControlledComposition>()
while (toRecompose.isNotEmpty() || toInsert.isNotEmpty()) {
...
toRecompose.fastForEach { composition ->
alreadyComposed.add(composition)
performRecompose(composition, modifiedValues)?.let {
toApply += it
}
}
...

// Find any trailing recompositions that need to be composed because
// of a value change by a composition. This can happen, for example, if
// a CompositionLocal changes in a parent and was read in a child
// composition that was otherwise valid.
if (modifiedValues.isNotEmpty()) {
synchronized(stateLock) {
knownCompositions.fastForEach { value ->
if (
value !in alreadyComposed &&
value.observesAnyOf(modifiedValues)
) {
toRecompose += value
}
}
}
}

if (toRecompose.isEmpty()) {
...
fillToInsert()
while (toInsert.isNotEmpty()) {
toLateApply += performInsertValues(toInsert, modifiedValues)
fillToInsert()
}
...
}
}

if (toApply.isNotEmpty()) {
changeCount++

// Perform apply changes
...
toComplete += toApply
toApply.fastForEach { composition ->
composition.applyChanges()
}
...
}

if (toLateApply.isNotEmpty()) {
...
toComplete += toLateApply
toLateApply.forEach { composition ->
composition.applyLateChanges()
}
...
}

if (toComplete.isNotEmpty()) {
...
toComplete.forEach { composition ->
composition.changesApplied()
}
...
}

synchronized(stateLock) {
deriveStateLocked()
}
}
}

discardUnusedValues()
}
}
}

平常在写代码时,经常用到 LaunchedEffect、SideEffect 是怎么样生效的?

首先我们来看比较简单的 SideEffect,可以看到将 SideEffect 函数记录在 RememberEventDispatcher 中,在 1.2 的部分我们知道,解析成渲染树后,通过 RememberEventDispatcher 回调了 SideEffect 执行,所以 SideEffect 是每次 (Re)Compose 过程中都会被执行,且是在主线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun SideEffect(
effect: () -> Unit
) {
currentComposer.recordSideEffect(effect)
}

internal class ComposerImpl(...): Composer {
...

override fun recordSideEffect(effect: () -> Unit) {
record { _, _, rememberManager -> rememberManager.sideEffect(effect) }
}

private class RememberEventDispatcher(
private val abandoning: MutableSet<RememberObserver>
) : RememberManager {
...
private val sideEffects = mutableListOf<() -> Unit>()

override fun sideEffect(effect: () -> Unit) {
sideEffects += effect
}
}
}

我们再来看相对复杂一些的 LaunchedEffect,同样的还是最终被记录在 RememberEventDispatcher 中,与 SideEffect 不同的是,LaunchedEffect 执行一次后会被移除,且是通过协程的方式启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit
) {
val applyContext = currentComposer.applyCoroutineContext
remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

internal class LaunchedEffectImpl(
parentCoroutineContext: CoroutineContext,
private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
private val scope = CoroutineScope(parentCoroutineContext)
private var job: Job? = null

override fun onRemembered() {
job?.cancel("Old job was still running!")
job = scope.launch(block = task)
}

override fun onForgotten() {
job?.cancel()
job = null
}

override fun onAbandoned() {
job?.cancel()
job = null
}
}

@ComposeCompilerApi
inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
@Suppress("UNCHECKED_CAST")
return rememberedValue().let {
if (invalid || it === Composer.Empty) {
val value = block()
updateRememberedValue(value)
value
} else it
} as T
}


internal class ComposerImpl(...): Composer {
...

@PublishedApi
@OptIn(InternalComposeApi::class)
internal fun updateValue(value: Any?) {
if (inserting) {
writer.update(value)
if (value is RememberObserver) {
record { _, _, rememberManager -> rememberManager.remembering(value) }
abandonSet.add(value)
}
} else {
val groupSlotIndex = reader.groupSlotIndex - 1
if (value is RememberObserver) {
abandonSet.add(value)
}
recordSlotTableOperation(forParent = true) { _, slots, rememberManager ->
if (value is RememberObserver) {
rememberManager.remembering(value)
}
when (val previous = slots.set(groupSlotIndex, value)) {
is RememberObserver ->
rememberManager.forgetting(previous)
is RecomposeScopeImpl -> {
val composition = previous.composition
if (composition != null) {
previous.release()
composition.pendingInvalidScopes = true
}
}
}
}
}
}

override fun rememberedValue(): Any? = nextSlot()

override fun updateRememberedValue(value: Any?) = updateValue(value)

private class RememberEventDispatcher(
private val abandoning: MutableSet<RememberObserver>
) : RememberManager {
private val remembering = mutableListOf<RememberObserver>()
private val forgetting = mutableListOf<RememberObserver>()

override fun remembering(instance: RememberObserver) {
forgetting.lastIndexOf(instance).let { index ->
if (index >= 0) {
forgetting.removeAt(index)
abandoning.remove(instance)
} else {
remembering.add(instance)
}
}
}

override fun forgetting(instance: RememberObserver) {
remembering.lastIndexOf(instance).let { index ->
if (index >= 0) {
remembering.removeAt(index)
abandoning.remove(instance)
} else {
forgetting.add(instance)
}
}
}
}
}

CompositionLocal 通过 @Composable 函数隐式向下传递数据,它又是怎么样发挥作用的?

使用 CompositionLocal 的方式是通过 CompositionLocalProvider 向下传递数据:

1
2
3
4
5
6
7
@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(vararg values: ProvidedValue<*>, content: @Composable () -> Unit) {
currentComposer.startProviders(values)
content()
currentComposer.endProviders()
}

通过 1.1 Composition 过程,我们知道 Composition 会将 @Composable 函数转化为基于代码位置的存储(Positional Memoization)数据,记录在 SlotTable 中。而其中记录的不只是 UI 的位置或者 UI 的状态,也包含一些值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
internal class ComposerImpl(
...
) : Composer {
...

private fun currentCompositionLocalScope(group: Int? = null): CompositionLocalMap {
if (group == null)
providerCache?.let { return it }
if (inserting && writerHasAProvider) {
var current = writer.parent
while (current > 0) {
if (writer.groupKey(current) == compositionLocalMapKey &&
writer.groupObjectKey(current) == compositionLocalMap
) {
@Suppress("UNCHECKED_CAST")
val providers = writer.groupAux(current) as CompositionLocalMap
providerCache = providers
return providers
}
current = writer.parent(current)
}
}
...
providerCache = parentProvider
return parentProvider
}

@InternalComposeApi
override fun startProviders(values: Array<out ProvidedValue<*>>) {
val parentScope = currentCompositionLocalScope()
startGroup(providerKey, provider)
// The group is needed here because compositionLocalMapOf() might change the number or
// kind of slots consumed depending on the content of values to remember, for example, the
// value holders used last time.
startGroup(providerValuesKey, providerValues)
val currentProviders = invokeComposableForResult(this) {
compositionLocalMapOf(values, parentScope)
}
endGroup()
val providers: CompositionLocalMap
val invalid: Boolean
if (inserting) {
providers = updateProviderMapGroup(parentScope, currentProviders)
invalid = false
writerHasAProvider = true
} else {
@Suppress("UNCHECKED_CAST")
val oldScope = reader.groupGet(0) as CompositionLocalMap

@Suppress("UNCHECKED_CAST")
val oldValues = reader.groupGet(1) as CompositionLocalMap

// skipping is true iff parentScope has not changed.
if (!skipping || oldValues != currentProviders) {
providers = updateProviderMapGroup(parentScope, currentProviders)

// ...
invalid = providers != oldScope
} else {
// Nothing has changed
skipGroup()
providers = oldScope
invalid = false
}
}

if (invalid && !inserting) {
providerUpdates[reader.currentGroup] = providers
}
providersInvalidStack.push(providersInvalid.asInt())
providersInvalid = invalid
providerCache = providers
start(compositionLocalMapKey, compositionLocalMap, false, providers)
}

@InternalComposeApi
override fun endProviders() {
endGroup()
endGroup()
providersInvalid = providersInvalidStack.pop().asBool()
providerCache = null
}
}

以上通过 Compositon 的过程将 CompositionLocalProvider 提供的数据记录在 slotTable 中,而在使用时,则通过调用 CompositionLocal 获取当前值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Stable
sealed class CompositionLocal<T> constructor(defaultFactory: () -> T) {
@Suppress("UNCHECKED_CAST")
internal val defaultValueHolder = LazyValueHolder(defaultFactory)

@Composable
internal abstract fun provided(value: T): State<T>

/**
* Return the value provided by the nearest [CompositionLocalProvider] component that invokes, directly or
* indirectly, the composable function that uses this property.
*
* @sample androidx.compose.runtime.samples.consumeCompositionLocal
*/
@OptIn(InternalComposeApi::class)
inline val current: T
@ReadOnlyComposable
@Composable
get() = currentComposer.consume(this)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
internal class ComposerImpl(
...
) : Composer {
...

@InternalComposeApi
override fun <T> consume(key: CompositionLocal<T>): T =
resolveCompositionLocal(key, currentCompositionLocalScope())

private fun currentCompositionLocalScope(group: Int? = null): CompositionLocalMap {
if (group == null)
providerCache?.let { return it }
...
if (reader.size > 0) {
var current = group ?: reader.parent
while (current > 0) {
if (reader.groupKey(current) == compositionLocalMapKey &&
reader.groupObjectKey(current) == compositionLocalMap
) {
@Suppress("UNCHECKED_CAST")
val providers = providerUpdates[current]
?: reader.groupAux(current) as CompositionLocalMap
providerCache = providers
return providers
}
current = reader.parent(current)
}
}
providerCache = parentProvider
return parentProvider
}
}

@Composable AndroidView 是怎么和 Compose 进行组合渲染的?

首先我们来看 AndroidView 的实现,由此可以见,虽然是原生的 View,但在 Compose 的体系中仍然是作为你一个 Node 节点来进行逻辑渲染的,不过与其他 Compsoe Node 不同的是,AndroidView 仍然需要借助 Android 视图体系来完成测量和布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun <T : View> AndroidView(
factory: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
) {
...
ComposeNode<LayoutNode, UiApplier>(
factory = {
val viewFactoryHolder = ViewFactoryHolder<T>(context, parentReference, dispatcher)
viewFactoryHolder.factory = factory
...
viewFactoryHolderRef.value = viewFactoryHolder
viewFactoryHolder.layoutNode
},
update = {
...
}
)
}

在 1.3 的渲染过程中,查看 AndroidComposeView 的源码我们可以看出来在,onMeasure 和 onLayout 的过程中都有 androidViewsHandler 的身影参与其中,那为什么 dispatchDraw 中反而没有了 androidViewsHandler 影子呢,这是因为上面提到的在 Drawing 的过程中,仍然是依靠 LayoutNode 渲染树来管理的,当真正需要 draw 的时候就回通过 AndroidComposeView 调用到 AndroidViewsHandler 的 drawView 函数完成绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
internal class AndroidViewsHandler(context: Context) : ViewGroup(context) {
init {
clipChildren = false
}

val holderToLayoutNode = hashMapOf<AndroidViewHolder, LayoutNode>()
val layoutNodeToHolder = hashMapOf<LayoutNode, AndroidViewHolder>()

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Layout will be handled by component nodes. However, we act like proper measurement
// here in case ViewRootImpl did forceLayout().
require(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY)
require(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY)
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec)
)
// Remeasure children, such that, if ViewRootImpl did forceLayout(), the holders
// will be set PFLAG_LAYOUT_REQUIRED and they will be relaid out during the next layout.
// This will ensure that the need relayout flags will be cleared correctly.
holderToLayoutNode.keys.forEach { it.remeasure() }
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
// Layout was already handled by component nodes, but replace here because
// the View system has forced relayout on children. This method will only be called
// when forceLayout is called on the Views hierarchy.
holderToLayoutNode.keys.forEach { it.layout(it.left, it.top, it.right, it.bottom) }
}

override fun requestLayout() {
// Hack to cleanup the dirty layout flag on ourselves, such that this method continues
// to be called for further children requestLayout().
cleanupLayoutState(this)
// requestLayout() was called by a child, so we have to request remeasurement for
// their corresponding layout node.
for (i in 0 until childCount) {
val child = getChildAt(i)
val node = holderToLayoutNode[child]
if (child.isLayoutRequested && node != null) {
node.requestRemeasure()
}
}
}

fun drawView(view: AndroidViewHolder, canvas: Canvas) {
view.draw(canvas)
}
}

至此,Compose 的渲染过程也分析完毕,在此之外,相信我们试图解答的问题也有了答案。希望以上内容能帮助大家在使用过程中对 Compose 有更深的理解,也能更加运用自如。