Compose 通过调用函数的形式刷新 UI,这些不同的 Compose 函数组合在一起刷新整个界面,称为组合。
因为 Compose 还有个响应式的特性,数据组成状态,每当状态发生变化,就会触发这些 Compose 函数重新执行,称之为重组。
在现代 Android 架构模型中,如下图所示,状态连接了数据和 UI。
在典型架构中,界面层的界面元素依赖于状态容器,而状态容器又依赖于来自数据层或可选网域层的类。
状态发生变化就会触发重组。重组具有很多特点,参考上一篇文章 《Compose 编程思想和重组》。
摆脱重组的影响
因为重组会重新执行 Compose 函数,函数中如果创建了对象、或是初始化数据等操作,都会重新执行一遍。
下面是个例子:
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
var state = "A"
Text(
text = "Hello $name, $state!",
modifier = modifier.clickable {
state = "B"
}
)
}
每次重新执行 Greeting 都会重新执行 var state = "A" ,但是,这样直接更新方法中声明的变量是不会触发重组的。
重组的更新条件包括:
- 任何可观察的状态变化都会触发重组。例如,使用
State或MutableState持有的状态发生变化时,会触发相关 Composable 函数的重组。 - 当
LiveData或Flow发出新值时,会触发相应 Composable 函数的重组。可以使用collectAsState或observeAsState将这些数据源转换为 Compose 的状态。 - 通过
remember和derivedStateOf等机制管理的外部数据源变化也会触发重组。 - 传递给 Composable 函数的参数发生变化时,也会触发重组。
更改此变量不会触发重组的原因是 Compose 并未跟踪此更改。
修改代码:
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
var state = mutableStateOf("A")
Text(
text = "Hello $name, $state!",
modifier = modifier.clickable {
state = "B"
}
)
}
使用 mutableStateOf 来使 Compose 函数跟踪此属性的更改,但是这样在点击时仍然不会更新为 B,因为每次重组会重置 state 属性,重新赋值,所以也不会更改为 B。
进一步修改代码:
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val state = remember {
mutableStateOf("A")
}
Text(
text = "Hello $name, ${state.value}!",
modifier = modifier.clickable {
state.value = "B"
}
)
}
remember 可以起到保护作用,防止状态在重组时被重置。
另一个很隐蔽的知识点是, remember 的作用域。看看下面的代码:
Column {
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
Greeting(
name = "MMA",
modifier = Modifier.padding(innerPadding)
)
}
这两个 Greeting 是互不影响的。也就是说 remember 的作用域在可执行函数的内部。
源码分析
我们来看看 remember 函数是如何实现在方法中保存数据的:
@Composable
inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
currentComposer.cache(false, calculation)
// ... 这里省略不同个 key 作为参数的同名不同参方法
@Composable
inline fun <T> remember(
vararg keys: Any?,
crossinline calculation: @DisallowComposableCalls () -> T
): T {
var invalid = false
for (key in keys) invalid = invalid or currentComposer.changed(key)
return currentComposer.cache(invalid, calculation)
}
首先分析最简单的:
@Composable
inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
currentComposer.cache(false, calculation)
首先是关键字:
inline说明这是一个内联函数crossinline确保calculation参数不会直接使用 return 结束逻辑,跳过外部函数的后续逻辑。
然后是注解:
@Composable:可组合函数内使用。@DisallowComposableCalls: 这将防止在应用该关键字的函数内部发生 Composable 调用。它通常应用于内联 Composable 函数的 lambda 参数,这些参数应该被内联,但不能安全地包含 Composable 调用。
另外这是一个泛型函数,可以根据传入类型输出一样的类型。
最后是重点 currentComposer.cache(false, calculation)。
在 Jetpack Compose 中,currentComposer 是一个重要的内部属性,它提供了对当前 Composer 实例的访问。Composer 是 Compose 框架内部用于管理组合过程的核心类,负责跟踪和处理 Compose 的 UI 树。
@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
}
Composer.cache(...) 是 Composer 的拓展函数,Composer 是一个密封接口(sealed interface)。
这里主要是返回了 rememberedValue 函数的值,并根据 invalid 参数判断值是否失效,失效的话分三步走:
val value = block(),直接执行传入的 block,拿到 block 返回的值;updateRememberedValue(value),调用Composer的updateRememberedValue函数更新值;return value,返回更新后的值。
第二步的 updateRememberedValue 在 ComposerImpl 中的实现是调用了下面这个函数:
override fun updateRememberedValue(value: Any?) = updateCachedValue(value)
@PublishedApi
@OptIn(InternalComposeApi::class)
internal fun updateCachedValue(value: Any?) {
val toStore = if (value is RememberObserver) {
if (inserting) { changeListWriter.remember(value) }
abandonSet.add(value)
RememberObserverHolder(value)
} else value
updateValue(toStore)
}
这里涉及的东西较多,不必深究,只需要知道作用是将插槽表中的当前值安排更新的新值。
回到失效验证,重要的方法是 rememberedValue 函数,这是 Composer 接口中声明的一个方法,实现是:
override fun rememberedValue(): Any? = nextSlotForCache()
@PublishedApi
@OptIn(InternalComposeApi::class)
internal fun nextSlotForCache(): Any? {
return if (inserting) {
validateNodeNotExpected()
Composer.Empty
} else reader.next().let {
if (reusing && it !is ReusableRememberObserver) Composer.Empty
else if (it is RememberObserverHolder) it.wrapped
else it
}
}
首先,ComposerImpl 有一个插槽表,slotTable 构造参数:
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: ChangeList,
private var lateChanges: ChangeList,
/**
* The composition that owns this composer
*/
override val composition: ControlledComposition
) : Composer
SlotTable 在 Jetpack Compose 中是一个关键的数据结构,用于存储和管理组合过程中的数据。它支持状态管理、数据生命周期管理以及高效的数据访问。
SlotTable 内容繁多,会单独写一篇文章来讲。还是分析一下 nextSlotForCache,点到为止。
首先这是用来返回一个值的,他会根据插入状态来判断,
-
插入中,首先使用
validateNodeNotExpected确保节点状态正常,然后返回一个Composer.Empty; -
其他状态(没有在插入),通过 SlotReader 读取下一个节点
-
然后判断如果
reusing(复用)为true并且获取到的值it不是ReusableRememberObserver类型,则返回Composer.Empty; -
否则,
- 如果获取到的值
it是RememberObserverHolder类型,则返回其包装的值it.wrapped; - 否则直接返回节点本身
- 如果获取到的值
-
“
ReusableRememberObserver: 可重用的记忆观察者,在组合重用/停用期间不会被移除。用于在组合停用期间保留组合局部变量。