共计 22166 个字符,预计需要花费 56 分钟才能阅读完成。
图片来自:https://developer.android.goo…
本文作者:goolong
Compose 是 Google 推出的现代化 UI 开发工具包,基于申明式 UI 开发格调,加上 @Composable 函数帮忙开发者无效的实现关注点拆散,另外 Compose 外部最大水平优化了重组范畴,能够帮忙咱们高效的刷新 UI,思考到 Compose 整体架构设计过于简单,这篇文章次要带大家理解 Compose Runtime 层外围的实现逻辑。
申明式 UI
申明式 UI 对于 Android 开发同学可能有点生疏,不过相熟 React 和 Flutter 的同学应该比较清楚,不论是 React、Flutter、Compose,外围都是 MVI 架构形式,通过数据驱动 UI,底层须要保护相应的 UI Tree,比方 React 的 VirtualDOM,Flutter 的 Element,而 Compose 的外围是 Composition。
所谓 “ 数据驱动 UI”,就是当 state 变动时,重建这颗树型构造并基于这棵 NodeTree 刷新 UI。当然,出于性能思考,当 NodeTree 须要重建时,各框架会应用 VirtualDom、GapBuffer(或称 SlotTable)等不同技术对其进行 “ 差量 ” 更新,防止 “ 全量 ” 重建。compose.runtime
的重要工作之一就是负责 NodeTree 的创立与更新。
@Composable
@Copmposable
并不是一个注解处理器,Compose 在 Kotlin 编译器的类型检测和代码生成阶段依赖 Kotlin 编译器插件工作,工作原理有点相似于 Kotlin Coroutine 协程的 suspend 函数,suspend 函数在 Kotlin 插件编译时生成带有 $continuation
参数(挂终点),而 Compose 函数生成带有参数 $composer
,因而 Compose 也被网友戏称为 “KotlinUI”。
相似于在 suspend 函数中能够调用一般函数和 suspend 函数,而一般函数中不能调用 suspend 函数,Compose 函数也遵循这一规定,正是因为一般函数中不带有 Kotlin 编译器生成的 $composer 参数。
fun Example(a: () -> Unit, b: @Composable () -> Unit) {a() // 容许
b() // 不容许}
@Composable
fun Example(a: () -> Unit, b: @Composable () -> Unit) {a() // 容许
b() // 容许}
生命周期
所有的 Compose 函数都是一个可组合项,当 Jetpack Compose 首次运行可组合项时,在初始组合期间,它将跟踪您为了形容组合中的界面而调用的可组合项。当利用的状态发生变化时,Jetpack Compose 会安顿重组,重组是指 Jetpack Compose 从新执行可能因状态更改而更改的可组合项,而后更新组合以反映所有更改。
参考 Google Jetpack 文档的例子:
@Composable
fun LoginScreen(showError: Boolean) {if (showError) {LoginError()
}
LoginInput() // This call site affects where LoginInput is placed in Composition}
@Composable
fun LoginInput() { /* ... */}
Compose NodeTree
后面介绍了 Compose 一些基础知识,Android 同学都晓得 View 体系中构建了一颗 View 树,而在 Compose 中也是这样,不过在 Compose 中有两颗树(相似于 React),一颗虚构树 SlotTable
(负责树构建和重组,相似 React 中的 VirtualDom),一颗实在的树 LayoutNode
(负责测量和绘制)。
首先咱们来看下 Compose UI 中如何构建 Layout 布局代码,间接看 setContent 办法。
internal fun ViewGroup.setContent(
parent: CompositionContext,
content: @Composable () -> Unit): Composition {GlobalSnapshotManager.ensureStarted() // 开启 snapshot 监听(十分重要,前面会讲到)val composeView =
if (childCount > 0) {getChildAt(0) as? AndroidComposeView
} else {removeAllViews(); null
} ?: AndroidComposeView(context).also {// 创立 AndroidComposeView,并增加到 ViewGroup()
addView(it.view, DefaultLayoutParams)
}
return doSetContent(composeView, parent, content)
}
@OptIn(InternalComposeApi::class)
private fun doSetContent(
owner: AndroidComposeView,
parent: CompositionContext,
content: @Composable () -> Unit): Composition {
...
val original = Composition(UiApplier(owner.root), parent) // 构建 Composition
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)
} // 包装成 WrappedComposition
wrapped.setContent(content)
return wrapped
}
content
函数 (例如 Text | Button) 最终调用了 Layout 函数,外围逻辑就是通过 ReusableComposeNode
创立 Node 节点。
@Composable
inline fun Layout(content: @Composable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
) {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
ReusableComposeNode<ComposeUiNode, Applier<Any>>(
factory = ComposeUiNode.Constructor, // factory 创立 Node 节点
update = { // update 更新 Node 节点内容
set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
set(density, ComposeUiNode.SetDensity)
set(layoutDirection, ComposeUiNode.SetLayoutDirection)
},
skippableUpdate = materializerOf(modifier),
content = content
)
}
从下面咱们能够看进去,Compose UI 如何基于 Compose Runtime 构建的具备树治理的 View 零碎(外部 LayoutNode 测量和绘制逻辑先疏忽掉), 上面咱们来基于 Compose Runtime 构建一个简略的树管理系统,比方实现上面这个简略的 Content
函数 。
@Composable
fun Content() {var state by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {delay(3000)
state = false
}
if (state) {Node1()
}
Node2()}
- 咱们先定义 Node 节点 (其中 Node1 和 Node2 都继承于 Node,Node 外部通过 children 保留子节点信息)
sealed class Node {val children = mutableListOf<Node>()
class RootNode : Node() {override fun toString(): String {return rootNodeToString()
}
}
data class Node1(var name: String = "",) : Node()
data class Node2(var name: String = "",) : Node()}
- 其次咱们须要自定义 NodeApplier 用来操作 Node 节点
class NodeApplier(node: Node) : AbstractApplier<Node>(node) {
...
override fun insertTopDown(index: Int, instance: Node) {current.children.add(index, instance) // 插入节点
}
override fun move(from: Int, to: Int, count: Int) {current.children.move(from, to, count) // 更新节点
}
override fun remove(index: Int, count: Int) {current.children.remove(index, count) // 移除节点
}
}
- 而后咱们须要定义 Compose 函数,(外部逻辑是通过
ReusableComposeNode
创立 Node 节点)
@Composable
private fun Node1(name: String = "node1") {
ReusableComposeNode<Node.Node1, NodeApplier>(
factory = {Node.Node1()
},
update = {set(name) {this.name = it}
}
)
}
@Composable
private fun Node2(name: String = "node2") {
ReusableComposeNode<Node.Node2, NodeApplier>(
factory = {Node.Node2()
},
update = {set(name) {this.name = it}
}
)
}
- 最初咱们来运行 Content 函数 ,这样咱们就利用 Compose Runtime 构建了一个简略的树管理系统
fun main() {val composer = Recomposer(Dispatchers.Main)
GlobalSnapshotManager.ensureStarted() // 监听
val mainScope = MainScope()
mainScope.launch(DefaultChoreographerFrameClock) {composer.runRecomposeAndApplyChanges() // Choreographer Frame 回调时开始重组
}
val rootNode = Node.RootNode()
Composition(NodeApplier(rootNode), composer).apply {
setContent {Content()
}
}
}
看到这里咱们大略明确了 Compose 构建流程,然而咱们心中可能还有一些疑难:
- Compose 函数外部调用流程是什么样的
- Compose 怎么构建生成 NodeTree,Node 节点信息怎么贮存的
- Compose 什么时候产生重组,重组过程中做了什么事件
- Compose 如何监听 State 变动并实现高效 diff 更新的
- Snapshot 的作用是什么
上面让咱们带着下面这些疑难,看看 Kotlin Compiler Plugin 编译后生成的代码
@Composable
public static final void Content(@Nullable Composer $composer, final int $changed) {
// ↓↓↓↓RestartGroup↓↓↓↓
$composer = $composer.startRestartGroup(-337788314);
ComposerKt.sourceInformation($composer, "C(Content)");
if ($changed == 0 && $composer.getSkipping()) {$composer.skipToGroupEnd();
} else {
// LaunchedEffect and MutableState related code
$composer.startReplaceableGroup(-337788167);
if (Content$lambda-2(state$delegate)) {Node1((String)null, $composer, 0, 1);
}
$composer.endReplaceableGroup();
Node2((String)null, $composer, 0, 1);
}
ScopeUpdateScope var18 = $composer.endRestartGroup();
// ↑↑↑↑RestartGroup↑↑↑↑
// ↓↓↓↓Register the function to be called again↓↓↓↓
if (var18 != null) {var18.updateScope((Function2)(new Function2() {public final void invoke(@Nullable Composer $composer, int $force) {MainKt.Content($composer, $changed | 1);
}
}));
}
// ↑↑↑↑Register the function to be called again↑↑↑↑
}
@Composable
private static final void Node1(final String name, Composer $composer, final int $changed, final int var3) {$composer = $composer.startRestartGroup(1815931657);
...
ScopeUpdateScope var10 = $composer.endRestartGroup();
if (var10 != null) {var10.updateScope((Function2)(new Function2() {public final void invoke(@Nullable Composer $composer, int $force) {MainKt.Node1(name, $composer, $changed | 1, var3);
}
}));
}
}
第一次看到下面的代码可能会有点懵,生成的 compose 函数外部插入了很多 $composer.startXXXGroup
和 $composer.endXXXGroup
模板代码,通过查看 Composer
实现类 ComposerImpl
,会发现所有 startXXXGroup
代码最终调用上面这个 start
办法
/**
* @param key: 编译器生成 Group 惟一值
* @param objectKey: 辅助 key,某些 Group 中会用到
* @param isNode: 是否有 Node 节点
* @param data:
*/
private fun start(key: Int, objectKey: Any?, isNode: Boolean, data: Any?) {
...
// slotTable 操作逻辑
}
start
办法外部外围逻辑是通过 SlotReader
和 SlotWriter
操作 SlotTable
,上述 Compose 函数外部生成的 $composer.startXXXGroup
和 $composer.endXXXGroup
模板代码就是构建 NodeTree,在 Composer
中针对不同的场景,能够生成不同类型的 Group。
startXXXGroup | 阐明 |
---|---|
startNode /startResueableNode | 插入一个蕴含 Node 的 Group。例如文章结尾 ReusableComposeNode 的例子中,显示调用了 startResueableNode,而后调用 createNode 在 Slot 中插入 LayoutNode |
startRestartGroup | 插入一个可反复执行的 Group,它可能会随着重组被再次执行,因而 RestartGroup 是重组的最小单元 |
startReplacableGroup | 插入一个能够被替换的 Group,例如一个 if/else 代码块就是一个 ReplaceableGroup,它能够在重组中被插入后者从 SlotTable 中移除 |
startMovableGroup | 插入一个能够挪动的 Group,在重组中可能在兄弟 Group 之间产生地位挪动 |
startReusableGroup | 插入一个可复用的 Group,其外部数据可在 LayoutNode 之间复用,例如 LazyList 中同类型的 Item |
接下来咱们来看看 SlotTable
内部结构:
SlotTable
SlotTable 外部存储构造外围的就是 groups
(group 分组信息,NodeTree 树治理)和 slots
(group 所对应的数据),那 SlotTable 是怎么实现树结构和如何治理的呢?
internal class SlotTable : CompositionData, Iterable<CompositionGroup> {
/**
* An array to store group information that is stored as groups of [Group_Fields_Size]
* elements of the array. The [groups] array can be thought of as an array of an inline
* struct.
*/
var groups = IntArray(0)
private set
/**
* An array that stores the slots for a group. The slot elements for a group start at the
* offset returned by [dataAnchor] of [groups] and continue to the next group's slots or to
* [slotsSize] for the last group. When in a writer the [dataAnchor] is an anchor instead of
* an index as [slots] might contain a gap.
*/
var slots = Array<Any?>(0) {null}
private set
}
groups
是一个 IntArray,每 5 个 Int 为一组形成一个 Group 的信息
key
: Group 在 SlotTable 中的标识,在 Parent Group 范畴内惟一Group info
: Int 的 Bit 位中存储着一些 Group 信息,例如是否是一个 Node,是否蕴含 Data 等,这些信息能够通过位掩码来获取。Parent anchor
: Parent 在 groups 中的地位,即绝对于数组指针的偏移( 树结构 )Size: Group
: 蕴含的 Slot 的数量Data anchor
:关联 Slot 在 slots 数组中的起始地位( 地位信息 )
咱们能够通过 SlotTable#asString()
办法打印对应的树结构信息,通过后面剖析,咱们晓得树结构是在 Kotlin Compiler Plugin 编译器生成的,通过 $composer#startXXXGroup
和 $composer#endXXXGroup
配对生成 Group 树结构。
Group(0) key=100, nodes=2, size=16, slots=[0: {}]
Group(1) key=1000, nodes=2, size=15
Group(2) key=200, nodes=2, size=14 objectKey=OpaqueKey(key=provider)
Group(3) key=-985533309, nodes=2, size=13, slots=[2: androidx.compose.runtime.RecomposeScopeImpl@4fb4ae6, androidx.compose.runtime.internal.ComposableLambdaImpl@3b52827]
Group(4) key=-337788314, nodes=2, size=12 aux=C(Content), slots=[5: androidx.compose.runtime.RecomposeScopeImpl@b882ad4]
Group(5) key=-3687241, nodes=0, size=1 aux=C(remember):Composables.kt#9igjgp, slots=[7: MutableState(value=false)@167707773]
Group(6) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[9: MutableState(value=false)@167707773, Function2<kotlinx.coroutines.CoroutineScope, kotlin.coroutines.Continuation<? super kotlin.Unit>, java.lang.Object>]
Group(7) key=1036442245, nodes=0, size=2 aux=C(LaunchedEffect)P(1)336@14101L58:Effects.kt#9igjgp
Group(8) key=-3686930, nodes=0, size=1 aux=C(remember)P(1):Composables.kt#9igjgp, slots=[13: kotlin.Unit, androidx.compose.runtime.LaunchedEffectImpl@8d3f428]
Group(9) key=-337788167, nodes=1, size=4
Group(10) key=1815931657, nodes=1, size=3, slots=[15: androidx.compose.runtime.RecomposeScopeImpl@7421fc3]
Group(11) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp
Group(12) key=125, nodes=0, size=1 node=Node1(name=node1), slots=[18: node1]
Group(13) key=1815931930, nodes=1, size=3, slots=[19: androidx.compose.runtime.RecomposeScopeImpl@81cf51f]
Group(14) key=1546164276, nodes=1, size=2 aux=C(ReusableComposeNode):Composables.kt#9igjgp
Group(15) key=125, nodes=0, size=1 node=Node2(name=node2), slots=[22: node2]
GapBuffer
GapBuffer(间隙缓冲区)这个概念个别在很多中央有用到,比方文本编辑器,它在内存中应用扁平数组(flat array)实现,这个数组比真正存储数据的汇合要大,而且在插入数据的会判断数据大小进行 gap 扩容,通过挪动 gap index 能够将 insert(增)、delete(删)、update(改)、get(查)操作的工夫复杂度降到 O(n)常数量级。
SlotTable 中挪动 gap 的办法详见 moveGroupGapTo 和 moveSlotGapTo
上面咱们来比照下没有 GapBuffer 和 GapBuffer 两种场景下删除一个节点和多个节点的效率,能够看到删除多个节点状况下 GapBuffer 的效率要远高于没有 GapBuffer;在没有 GapBuffer 的状况下,在 Array 中只能每次挪动一个 Node,insert 和 delete 节点工夫效率是 O(nLogN),然而有 GapBuffer 状况下,能够通过挪动 gap 的地位,将工夫效率优化到 O(n)。
没有 GapBuffer | 有 GapBuffer | |
---|---|---|
删除一个节点 | ||
删除多个节点 |
Snapshot
Snapshot 是一个 MVCC(Multiversion Concurrency Control,多版本并发管制) 的实现,个别 MVCC 用于数据库中实现事务并发,还有分布式版本控制系统(常见的 Git 和 SVN),上面简略看下 Snapshot 应用。
fun test() {
// 创立状态(主线开发)val state = mutableStateOf(1)
// 创立快照(开分支)val snapshot = Snapshot.takeSnapshot()
// 批改状态(主线批改状态)state.value = 2
println(state.value) // 打印 1
snapshot.enter {// 进入快照(切换分支)// 读取快照状态(分支状态)println(state.value) // 打印 1
}
// snapshot.apply() 保留快照(上面 print statr 打印 1)// 读取状态(主线状态)println(state.value) // 打印 2
// 废除快照(删除分支)snapshot.dispose()}
另外 Snapshot 提供了 registerGlobalWriteObserver
和 registerApplyObserver
用来监听全局 Snapshot 写入和 apply 回调,理论同时在 MutableSnapshot 构造函数传入的。
open class MutableSnapshot internal constructor(
id: Int,
invalid: SnapshotIdSet,
override val readObserver: ((Any) -> Unit)?, // 读取监听
override val writeObserver: ((Any) -> Unit)? // 写入监听
) : Snapshot(id, invalid)
如果不间接复用零碎封装好的,咱们也能够本人创立 Snapshot,并注册告诉。
class ViewModel {val state = mutableStateOf("initialized")
}
fun main() {val viewModel = ViewModel()
Snapshot.registerApplyObserver { changedSet, snapshot ->
changedSet.forEach {println("registerApplyObserver:" + it)
}
}
viewModel.state.value = "one"
Snapshot.sendApplyNotifications() //}
回到咱们之前提到的 GlobalSnapshotManager.ensureStarted()
,实际上就是通过 Snapshot 状态扭转告诉 Composition 重组。
internal object GlobalSnapshotManager {private val started = AtomicBoolean(false)
fun ensureStarted() {if (started.compareAndSet(false, true)) {val channel = Channel<Unit>(Channel.CONFLATED)
CoroutineScope(AndroidUiDispatcher.Main).launch {
channel.consumeEach {Snapshot.sendApplyNotifications() // 发送告诉 applyChanges
}
}
Snapshot.registerGlobalWriteObserver {channel.trySend(Unit) // 监听全局 Snapshot 写入
}
}
}
}
下面大略理解了 SlotTable
构造和 NodeTree 构建流程,上面看看这段代码:
@Composable
fun Content() {var state by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {delay(3000)
state = false
}
...
}
预计大家应该能看懂这段代码逻辑是创立一个 state,而后在 3 秒后更新 state 的值,然而大家肯定存在几个纳闷
remember
函数的作用是什么LaunchedEffect
函数作用是啥,外面能够调用delay
函数,是不是与协程有关系- 通过
mutableStateOf
创立的 State,为啥能够告诉 Compose 进行重组
下面波及到的 remember
| LaunchedEffect
| State
与 Compose 重组存在紧密联系,上面让咱们一起来看看 Compose 重组是如何实现的
Compose 重组
@Composable 函数是纯函数,纯函数是幂等的,惟一输出对应惟一输入,且不应该蕴含任何副作用(比方批改全局变量或反注册监听等),为了保护 @Composable 纯函数语义,Compose 提供了 state、remember、SideEffect、CompositionLocal 这些实现,相似于 React 提供的各种 Hook。
Remember
间接来看下 remember
函数定义,主要参数是 key 和 calculation,Composer
依据 key 变动判断是否从新调用 calculation 计算值
inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T
inline fun <T> remember(key1: Any?, calculation: @DisallowComposableCalls () -> T): T
inline fun <T> remember(key1: Any?, key2: Any?, calculation: @DisallowComposableCalls () -> T): T
inline fun <T> remember(key1: Any?, key2: Any?, key3: Any?, calculation: @DisallowComposableCalls () -> T): T
inline fun <T> remember(vararg keys: Any?, calculation: @DisallowComposableCalls () -> T): T
remember
外部调用的 composer#cache
办法,key 是否变动调用的 composer#changed
办法。
inline fun <T> Composer.cache(invalid: Boolean, block: () -> T): T {@Suppress("UNCHECKED_CAST")
return rememberedValue().let {if (invalid || it === Composer.Empty) {val value = block()
updateRememberedValue(value)
value
} else it
} as T
}
@ComposeCompilerApi
override fun changed(value: Any?): Boolean {return if (nextSlot() != value) {updateValue(value)
true
} else {false}
}
rememberedValue
间接调用 nextSlot
办法,updateRememberedValue
间接调用 updateValue
办法,外围逻辑就是通过 SlotReader
和 SlotWriter
操作 SlotTable
存储数据,而且这些数据是能够跨 Group 的,具体细节能够本人查看源码。
State
State
接口定义很简略,理论开发过程中都是调用 mutableStateOf
创立 MutableState
。
fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy() // snapshot 比拟策略): MutableState<T> = createSnapshotMutableState(value, policy)
internal actual fun <T> createSnapshotMutableState(
value: T,
// SnapshotMutationPolicy 有三个实现 StructuralEqualityPolicy(值相等)|ReferentialEqualityPolicy(同一个对象)|NeverEqualPolicy(永不雷同)policy: SnapshotMutationPolicy<T>
): SnapshotMutableState<T> = ParcelableSnapshotMutableState(value, policy)
ParcelableSnapshotMutableState
继承自 SnapshotMutableStateImpl
,本身实现 Parcelable
内存序列化,所以咱们间接剖析 SnapshotMutableStateImpl
。
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {@Suppress("UNCHECKED_CAST")
override var value: T
get() = next.readable(this).value
set(value) = next.withCurrent { // 外部
if (!policy.equivalent(it.value, value)) {next.overwritable(this, it) {this.value = value}
}
}
private var next: StateStateRecord<T> = StateStateRecord(value) // 继承 StateRecord
override val firstStateRecord: StateRecord
get() = next
override fun prependStateRecord(value: StateRecord) {@Suppress("UNCHECKED_CAST")
next = value as StateStateRecord<T>
}
@Suppress("UNCHECKED_CAST")
override fun mergeRecords(
previous: StateRecord,
current: StateRecord,
applied: StateRecord
): StateRecord? {
...
// snapshot 分支抵触解决合并逻辑,最终后果与 policy 相干
}
}
能够看到真正的外围类是 StateObject
,StateObject 外部存储构造是 StateRecord
,外部应用链表存储,通过 Snapshot 治理 State 值,最终调用 mergeRecords
解决抵触逻辑(与 SnapshotMutationPolicy 值相干)。
abstract class StateRecord {internal var snapshotId: Int = currentSnapshot().id // snapshotId,版本治理
internal var next: StateRecord? = null // 外部存储构造是链表
abstract fun assign(value: StateRecord) // 将 value 赋值给以后 StateRecord
abstract fun create(): StateRecord // 创立新的 StateRecord}
SideEffect
副作用是指 Compose 外部除了状态变动之外的利用状态的变动,比方页面申明周期 Lifecycle 或播送等场景,须要在页面不可见或播送登记时扭转一些利用状态防止内存透露等,相似于 Coroutine 协程中提供的 suspendCancellableCoroutine
在 invokeOnCancel
中做一些状态批改的工作,Effect 分为以下三类:
第一类是 SideEffect
,实现形式比较简单,调用流程是 composer#recordSideEffect
-> composer#record
,间接往 Composer 中 changes
插入 change
,最终会在 Composition#applychanges
回调 effect
函数。
@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun SideEffect(effect: () -> Unit
) {currentComposer.recordSideEffect(effect)
}
internal class CompositionImpl(...) : ControlledComposition {
...
override fun applyChanges() {synchronized(lock) {val manager = RememberEventDispatcher(abandonSet) // RememberManager 实现类
try {applier.onBeginChanges()
// Apply all changes
slotTable.write { slots ->
val applier = applier
// 遍历 changes 而后 invoke 注入,能够查看 ComposerImpl#recordSideEffect 办法
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() // RememberObserver 的 onForgotten 或 onRemembered 被调用
manager.dispatchSideEffects() // SideEffect 调用
if (pendingInvalidScopes) {
pendingInvalidScopes = false
observations.removeValueIf {scope -> !scope.valid}
derivedStates.removeValueIf {derivedValue -> derivedValue !in observations}
}
} finally {manager.dispatchAbandons() // RememberObserver 的 onAbandoned 被调用
}
drainPendingModificationsLocked()}
}
...
}
第二类是 DisposableEffect
,DisposableEffectImpl 实现了 RememberObserver 接口,借助于 remember
存储在 SlotTable 中,并且 Composition 产生重组时会通过 RememberObserver#onForgotten
回调到 effect
的 onDispose
函数。
@Composable
@NonRestartableComposable
fun DisposableEffect(
key1: Any?,
effect: DisposableEffectScope.() -> DisposableEffectResult) {remember(key1) {DisposableEffectImpl(effect) }
}
第三类是 LaunchedEffect
,与 DisposableEffect
的次要区别是外部开启了协程,用来异步计算的。
@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit) {
val applyContext = currentComposer.applyCoroutineContext
remember(key1) {LaunchedEffectImpl(applyContext, block) }
}
CompositionLocal
在 WrappedComposition#setContent
咱们看到有调用 CompositionLocalProvider,在 ProvideCommonCompositionLocals
外部中定义了很多 CompositionLocal,次要性能是在 content
函数外部调用其余 Compose 函数时,能够快捷获取一些全局服务。
private class WrappedComposition(
val owner: AndroidComposeView,
val original: Composition
) : Composition, LifecycleEventObserver {
private var disposed = false
private var addedToLifecycle: Lifecycle? = null
@OptIn(InternalComposeApi::class)
override fun setContent(content: @Composable () -> Unit) {
owner.setOnViewTreeOwnersAvailable {if (!disposed) {
val lifecycle = it.lifecycleOwner.lifecycle
lastContent = content
if (addedToLifecycle == null) {
addedToLifecycle = lifecycle
// this will call ON_CREATE synchronously if we already created
lifecycle.addObserver(this)
} else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
original.setContent {
...
CompositionLocalProvider(LocalInspectionTables provides inspectionTable) {ProvideAndroidCompositionLocals(owner, content) // CompositionLocal 注入
}
}
}
}
}
}
}
@Composable
internal fun ProvideCommonCompositionLocals(
owner: Owner,
uriHandler: UriHandler,
content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalAccessibilityManager provides owner.accessibilityManager,
LocalAutofill provides owner.autofill,
LocalAutofillTree provides owner.autofillTree,
LocalClipboardManager provides owner.clipboardManager,
LocalDensity provides owner.density,
LocalFocusManager provides owner.focusManager,
LocalFontLoader provides owner.fontLoader,
LocalHapticFeedback provides owner.hapticFeedBack,
LocalLayoutDirection provides owner.layoutDirection,
LocalTextInputService provides owner.textInputService,
LocalTextToolbar provides owner.textToolbar,
LocalUriHandler provides uriHandler,
LocalViewConfiguration provides owner.viewConfiguration,
LocalWindowInfo provides owner.windowInfo,
content = content
)
}
CompositionLocal
作用是为了防止组合函数间传递显式参数,这样能够通过隐式参数传递给被调用的组合函数,其外部实现也是利用了 SlotTable 存储数据。
@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> //
@OptIn(InternalComposeApi::class)
inline val current: T
@ReadOnlyComposable
@Composable
get() = currentComposer.consume(this) // 获取以后 CompositionLocalScope 对应的值
}
定义好 CompositionLocal 之后,须要通过 CompositionLocalProvider
办法绑定数据,ProvidedValue
能够通过 ProvidableCompositionLocal 提供的中断办法 provides
返回。
@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(vararg values: ProvidedValue<*>, content: @Composable () -> Unit) {currentComposer.startProviders(values) // 在 SlotTable 的 groups 插入 key 为 providerKey 和 providerValuesKey 的 group 数据
content()
currentComposer.endProviders()}
接着来看下 CompositionLocal 如何获取数据,通过代码看到间接通过 composer#consume
返回,而 consume 办法外部最终还是通过 CompositionLocalMap
(理论是一个 PersistentMap<CompositionLocal<Any?>, State<Any?>> 构造)获取数据,其在 SlotTable 中对应的 groupKey 是 compositionLocalMapKey。
@Stable
sealed class CompositionLocal<T> constructor(defaultFactory: () -> T) {
...
inline val current: T
@ReadOnlyComposable
@Composable
get() = currentComposer.consume(this)
}
internal class ComposerImpl(...) {
...
override fun <T> consume(key: CompositionLocal<T>): T =
resolveCompositionLocal(key, currentCompositionLocalScope())
private fun <T> resolveCompositionLocal(
key: CompositionLocal<T>,
scope: CompositionLocalMap
): T = if (scope.contains(key)) {scope.getValueOf(key)
} else {key.defaultValueHolder.value}
...
}
看到这里咱们大略明确了 CompositionLocal 实现逻辑:
- 首先定义 CompositionLocal
- 通过 CompositionLocalProvoder 办法在 compose 函数嵌入插入
composer#startProviders
和composer#endProviders
,最终在 SlotTable 存入数据 - 通过
composer#consume
获取之前在 SlotTable 中插入的数据 - 在 Compose 函数外部能够从新赋值,不过只在本身和子 Compose 函数外部失效
CompositionLocal 有两种实现, 第一种是 StaticProvidableCompositionLocal,全局放弃不变(比方 LocalDensity 屏幕像素密度不随 Compose 函数层级而扭转)。
internal class StaticProvidableCompositionLocal<T>(defaultFactory: () -> T) :
ProvidableCompositionLocal<T>(defaultFactory) {
@Composable
override fun provided(value: T): State<T> = StaticValueHolder(value) // 返回一个常量
}
第二种是 DynamicProvidableCompositionLocal,能够在 Compose 函数外部扭转其值,而后告诉 Compose 重组并获取到最新的值。
internal class DynamicProvidableCompositionLocal<T> constructor(
private val policy: SnapshotMutationPolicy<T>,
defaultFactory: () -> T) : ProvidableCompositionLocal<T>(defaultFactory) {
@Composable
override fun provided(value: T): State<T> = remember {mutableStateOf(value, policy) }.apply {this.value = value} /// 通过 remember 返回 MutableState
}
总结
到这里咱们就根本明确了 Compose 是怎么实现的,最初回到咱们之前的问题:
-
Compose 函数外部调用流程是什么样的
- Kotlin Compiler Plugin 在编译阶段帮忙生成
$composer
参数的一般函数(有些场景还有带有$change
等辅助参数),外部调用的 Compose 函数传递$composer
参数
- Kotlin Compiler Plugin 在编译阶段帮忙生成
-
Compose 怎么构建生成 NodeTree,Node 节点信息怎么贮存的
- Kotlin Compiler Plugin 在 Compose 函数前后插入
startXXXGroup
和endXXXGroup
构建树结构,外部通过 SlotTable 实现 Node 节点数据存储和 diff 更新,SlotTable 通过groups
存储分组信息 和slots
存储数据
- Kotlin Compiler Plugin 在 Compose 函数前后插入
-
Compose 如何监听 State 变动并实现高效 diff 更新的
- MutableState 实现了 StateObject,外部借助 Snapshot 实现外部值更新逻辑,而后通过
remember
函数存储到 SlotTable 中,当 State 的值产生扭转时,Snapshot 会告诉到 Composition 进行重组
- MutableState 实现了 StateObject,外部借助 Snapshot 实现外部值更新逻辑,而后通过
-
Compose 什么时候产生重组,重组过程中做了什么事件
- 当 State 状态值产生扭转时,会借助 Snapshot 告诉到 Composition 进行重组,而重组的最小单位是 RestartGroup(Compose 函数编译期插入的
$composer.startRestartGroup
),通过 Kotlin Compiler Plugin 编译后的代码咱们发现,重组其实就是从新执行对应的 Compose 函数,通过 Group key 扭转 SlotTable 内部结构,最终反映到 LayoutNode 从新展现到 UI 上
- 当 State 状态值产生扭转时,会借助 Snapshot 告诉到 Composition 进行重组,而重组的最小单位是 RestartGroup(Compose 函数编译期插入的
-
Snapshot 的作用是什么
- Compose 重组借助了 Snapshot 实现并发执行,并且通过 Snapshot 读写确定下次重组范畴
参考资料:
- 深刻详解 Jetpack Compose | 优化 UI 构建
- 深刻详解 Jetpack Compose | 实现原理
- 摸索 Compose 内核:深刻 SlotTable 零碎
- 一文看懂 Jetpack Compose 快照零碎
- Understanding Jetpack Compose — part 1 of 2
- Understanding Jetpack Compose — part 2 of 2
本文公布自网易云音乐技术团队,文章未经受权禁止任何模式的转载。咱们长年招收各类技术岗位,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe(at)corp.netease.com!