diff --git a/2023/01/23/compose-render-seq/index.html b/2023/01/23/compose-render-seq/index.html index 5ee4561..cb83b66 100644 --- a/2023/01/23/compose-render-seq/index.html +++ b/2023/01/23/compose-render-seq/index.html @@ -39,11 +39,11 @@

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
}

...
}
+
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()
}
}
}
+
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
}
}
}

...
}
+
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 中。

@@ -54,16 +54,16 @@

1.2 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
}
...
}
}
+
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)
}
}
}
}
+
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)
}

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

...
}
}
+
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()
}
}
}
}
+
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 又是一块非常复杂的内容,知识储备有限,此处不展开。

@@ -73,16 +73,16 @@

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() {
...
})
}
}
}
+
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
}

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

...
}
+
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 的调用时机。

1.3 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()
}
}
}
+
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 的过程是被当作普通节点来处理的。

1.4 核心类关系图

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

compose-class.png

@@ -90,24 +90,26 @@

总结

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

作为一种新的 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()
}
}
}
+

除了上面我们分析到的与 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
}
}
}
+
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
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
}
}
+
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
}
}
+
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)
}
}