图片来自:developer.android.google.cn/jetpack/com…
本文作者: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!