文章目录
Compose 怎么更新界面?
Compose 既然是声明式 UI,那肯定就不能像传统 UI 那样命令式的方式更新界面,声明式的界面更新就是,你把值改了界面就会自动更新,你的参数会被 Compose 自动监听。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 注意这里是用 =
// mutableStateOf 返回的 MutableState 对象
val name = mutableStateOf("name")
setContent {
// 赋值使用 name.value
Text(name.value)
}
lifecycleScope.launch {
delay(3000)
// 修改数值,就能通知到 Composable 刷新
name.value = "vincent"
}
}
}
上面是一个文字的 Composable,开启了一个协程 3s 后修改 name 的值,Compose 监听到 name 改变了,就会同步将 Text() 也更新。
需要注意的是,在 Compose 中要实现参数被监听,参数要使用 Compose 提供的 MutableState 类型才能被监听,在写法上使用 mutableStateOf 函数创建:
val name = mutableStateOf("name")
SnapshotState.kt
@Stable
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
Compose 订阅更新原理
为什么通过 mutableStateOf 函数返回的对象就能监听实现值改变 Composable 的内容也跟着改变?
根据我们以往的开发经验,我们可以先猜测下 Compose 会怎么实现这个逻辑:
-
数据更新就会通知 Composable 刷新改变,也就是可能会涉及到观察者模式,会需要数据的监听和订阅
-
是什么时候完成的订阅和监听?又是怎样完成的通知刷新?
我们带着上面两个问题,详细讲解下 mutableStateOf 的原理。
在上面有提到,获取 MutableState 是通过 mutableStateOf 函数创建:
SnapshotState.kt
fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
internal actual fun <T> createSnapshotMutableState(
value: T,
policy: SnapshotMutationPolicy<T>
): SnapshotMutableState<T> = ParcelableSnapshotMutableState(value, policy)
ParcelableSapshotMutableState.kt
@SuppressLint("BanParcelableUsage")
internal class ParcelableSnapshotMutableState<T>(
value: T,
policy: SnapshotMutationPolicy<T>
) : SnapshotMutableStateImpl<T>(value, policy), Parcelable {
...
}
mutableStateOf 会创建 ParcelableSnapshotMutableState。
看 ParcelableSnapshotMutableState 源码时你可能会很疑惑:怎么 ParcelableSnapshotMutableState 的代码没有监听或修改 value 相关的实现?
实际上相关的逻辑都在父类 SnapshotMutableStateImpl:
SnapshotState.kt
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
// 重写了 value 的 getter/setter
@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)
override val firstStateRecord: StateRecord
get() = next
...
}
在 SnapshotMutableStateImpl 看到了对数值 value 的 getter/setter 被重写了。当写的时候就是调用重写的 setter 通知 Compose 刷新。
什么是 Compose 的刷新?接下来简单了解下。
Compose 的刷新
Compose 的刷新是由三部分组成:
-
组合(Composition,动词就是 Compose)
-
布局
-
绘制
Composition 组合的过程其实就是执行添加了 @Composable 注解的函数的过程,与传统 UI 的测量/布局、绘制相比多了前面第一步。
为什么要多一个组合的过程?
这是因为 用 @Composable 注解的函数并不是界面元素,而是用于生成界面的元素,所以组合过程其实是一个 “拼凑出界面内容” 的过程。
我们写的 Android 代码是运行在 Android 平台,而在 Android 平台要显示内容只有一个办法,就是通过 View 系统,实际上我们写的 Composable 最终都会被装载进一个 ComposeView,而它里面又会包着一个 AndroidComposeView,而 AndroidComposeView 会用 LayoutNode 进行真正的布局绘制,这些 LayoutNode 就是在上面提到的组合过程生成的。关于这块的原理都是后话,在后续的篇章会讲解到。
StateRecord、StateObject 与订阅的关系
mutableStateOf 的订阅行为涉及了两个比较重要的类:StateRecord 和 StateObject。
SnapshotState.kt
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
...
}
可以看到 SnapshotMutableStateImpl 实现了两个接口:StateObject 和 SnapshotMutableState。
interface SnapshotMutableState<T> : MutableState<T> {
/**
* A policy to control how changes are handled in a mutable snapshot.
*/
val policy: SnapshotMutationPolicy<T>
}
SnapshotMutableState 就是 MutableState 的子类,接口并没有订阅监听相关的代码,也就是说 MutableState 本身并没有订阅的功能。StateObject 会记录当前值或者说 Compose 的状态。
interface StateObject {
// 每个 StateRecord 就是 Compose 的状态
// 链表头节点,有头节点就能访问整个链表
val firstStateRecord: StateRecord
...
}
abstract class StateRecord {
// 指向链表的下一个节点
internal var next: StateRecord? = null
...
}
StateObject 有一个重要的变量 firstStateRecord,它是 StateRecord 类型,firstStateRecord 是一个链表结构,通过链表的方式链式记录状态,firstStateRecord 也是链表的头节点;StateRecord 的变量 next 是链表的下一个节点。
也就是说,StateObject 持有了 StateRecord 链表,一个 StateObject 对应多个 StateRecord,每个 StateRecord 记录当前 Compose 的状态。
在我们认知中,值改变了,只要替换成新值就行了,Compose 为什么不提供一个变量直接把值放在 SnapshotMutableStateImpl,而是另外的用链表记录所有状态变化,把新值旧值都存了起来?
因为 在 Compose 旧值也是有用的,Compose 支持事务功能,对于什么是事务功能会在后续讲到,现在只需要记住事务功能就是支持批量更新,更新可以撤销,要撤销就要有旧值需要存旧值。
简单总结下 StateRecord 和 StateObject:
-
一个 StateRecord 可以理解为某一时刻 Compose 当前的状态
-
StateObject 持有 StateRecord 链表即记录着 Compose 的各个状态变动
SnapshotMutableStateImpl.value 的 getter 完成订阅行为
Composable 获取数据时是调用 MutableState 的 value 的 getter:
SnapshotState.kt
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>
) : StateObject, SnapshotMutableState<T> {
@Suppress("UNCHECKED_CAST")
override var value: T
// next 是 StateStateRecord
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)
override val firstStateRecord: StateRecord
get() = next
// StateStateRecord 是 StateRecord 的子类
private class StateStateRecord<T>(myValue: T) : StateRecord() {
override fun assign(value: StateRecord) {
@Suppress("UNCHECKED_CAST")
this.value = (value as StateStateRecord<T>).value
}
override fun create(): StateRecord = StateStateRecord(value)
var value: T = myValue
}
...
}
value 的 getter 是调用 next.readable(this).value 获取数值。这个操作又可以拆解为两部分:
-
next 到底是什么?
-
next.readable(this) 是什么操作?返回了什么对象?
在上个节点了解到 StateObject 持有 StateRecord 链表记录着 Compose 当前状态这个知识点,下面就能很好的理解 next 到底是什么:
SnapshotState.kt
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 }
}
}
// firstStateRecord 是 StateObject 的成员,在这里重写了 getter
// firstStateRecord 是头节点的原因是因为 next 是链表头节点
override val firstStateRecord: StateRecord
get() = next
// StateStateRecord 是 StateRecord 的子类
// StateRecord 有 next 成员变量指向下一个 StateRecord 节点
// 所以可以理解为 next 就是一个链表
private var next: StateStateRecord<T> = StateStateRecord(value)
...
}
next 才是实际的链表头节点,firstStateRecord 的 getter 取值是 next,所以 firstStateRecord 是链表头节点。
已经知道 next 是 StateStateRecord,继续看它调用 readable() 做了什么事情:
Snapshot.kt
// next.readable(this)
// state 参数就是 SnapshotMutableStateImpl,同样它也是 StateObject
fun <T : StateRecord> T.readable(state: StateObject): T {
val snapshot = Snapshot.current
// snapshot.readObserver?.invoke(state) 就是实际做记录的操作,也就是订阅
snapshot.readObserver?.invoke(state)
return readable(this, snapshot.id, snapshot.invalid) ?: sync {
val syncSnapshot = Snapshot.current
readable(this, syncSnapshot.id, syncSnapshot.invalid)
} ?: readError()
}
snapshot.readObserver?.invoke(state) 就是实际的订阅操作,记录 MutableState 是在哪里被调用:
// name 是 MutableState 对象,执行 name.value 时会调用 value 的 getter
// getter 会调用 snapshot.readObserver?.invoke(state) 记录,也就是订阅行为
// 此时就会记录该 MutableState 被这个 Composable 使用
setContent {
Text(name.value)
}
这一步订阅的作用是,当 name.value 被写时,就会去找到所有订阅使用的位置然后标记为失效,等到下一帧这些失效的地方就会被刷新重新执行,更确切的说是会被 Recompose 重组。
Recompose 的范围是 Composable 所在的 lambda,例如上面例子是 setContent 的 lambda 内都会 Recompose。
fun <T : StateRecord> T.readable(state: StateObject): T {
val snapshot = Snapshot.current
snapshot.readObserver?.invoke(state)
// 三参数 readable() 遍历 StateRecord 链表,找到最新的、可用的 StateRecord
return readable(this, snapshot.id, snapshot.invalid) ?: sync {
val syncSnapshot = Snapshot.current
readable(this, syncSnapshot.id, syncSnapshot.invalid)
} ?: readError()
}
完成订阅后,又调用了一个 三参数的 readable() 函数,它的作用是遍历 StateRecord 链表,找到一个最新的、可用的 StateRecord。
总结下 value 的 getter 主要有两个操作:
-
记录 MutableState 被哪些 Composable 使用,该操作是订阅行为
-
返回一个最新的、可用的 StateRecord
简单总结下 mutableStateOf 函数完成订阅的要点:
-
mutableStateOf 返回的对象(MutableState)可以被订阅,是因为返回对象的成员变量 value 的 getter 被定制了
-
每次调用 getter 时都会记录 MutableState 被哪些 Composable 使用,记录这个步骤就是完成订阅的行为
-
订阅的作用是当调用 setter 时就会标记订阅位置的数值失效,要在下一帧时刷新 Compose
-
订阅完成后,在 StateRecord 链表找到一个最新的、可用的 StateRecord 返回
我们结合一开始的例子再说明下订阅操作:
Snapshot 与 StateRecord 的关系
无论是订阅的时候还是接下来要讲的 value 的 setter,都有涉及到一个概念:Snapshot,也就是快照。
StateRecord 每个修改的新旧值都会被串起来形成一个链表,链表的各个节点都代表了某一时刻 Compose 的整个内部状态,StateRecord 链表上的哪些节点共属于同一个状态也有记录,用的就是 Snapshot,每个 StateRecord 都有对应的 Snapshot id。
Snapshot 代表某一时刻 Compose 整体状态,一个 Snapshot 可以对应多个 StateRecord,一个 StateRecord 对应一个 Snapshot。
有了 Snapshot 后就可以在一些变量值发生变化的时候,不必马上将 StateRecord 改动应用到内部直接显示到界面,而是可以在跑完整个 Compose 流程后把所有要改变的变量一起应用,直接拿最终的结果进行布局和绘制,性能也会好一些。Snapshot 的机制对这种批量应用改变提供了下层技术可行性的支持。
按上面的描述可能还是不好理解什么是 Snapshot,依然用一开始的例子讲解:
// 初始值:SnapshotId=1,MutableState 对应的 StateRecord 的 SnapshotId=1
// 变化:SnapshotId=2,SnapshotId=2 没有 StateRecord,从 SnapshotId=1 拿对 SnapshotId=2 还是有效的 StateRecord
val name = mutableStateOf("name")
setContent {
Text(name.value)
}
lifecycleScope.launch {
delay(3000)
name.value = "vincent"
}
-
假设现在初始值的 SnapshotId=1,MutableState 对应的 StateRecord 的 SnapshotId=1
-
在某个时刻 SnapshotId=2,SnapshotId=2 此时没有对应的 StateRecord,但是 SnapshotId=1 的 StateRecord 对 SnapshotId=2 而言是可用有效的,那么 SnapshotId=2 就可以从 SnapshotId=1 拿那个可用有效的 StateRecord
-
因为 SnapshotId=2 是建立在 SnapshotId=1 的基础上,这也是 SnapshotId=2 能拿 SnapshotId=1 的 StateRecord 使用的原因。
StateRecord 和 Snapshot 的关系:
-
系统有多个 Snapshot 的时候,它们是有先后关系的,新的 Snapshot 是在上一个 Snapshot 基础上创建
-
同一个 StateObject 的每个 StateRecord,都有它们对应的 Snapshot 的 id。StateRecord 和 Snapshot 就算不直接对应,只要 Snapshot 的 StateRecord 对另一个 Snapshot 是有效的,另一个就能取到这个 StateRecord
SnapshotMutableStateImpl.value 的 setter
SnapshotState.kt
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
// next.withCurrent 调用的三参数的 readable() 返回一个最新的可用的 StateRecord
set(value) = next.withCurrent {
// 比较两个值是否相同
if (!policy.equivalent(it.value, value)) {
next.overwritable(this, it) { this.value = value }
}
}
}
Snapshot.kt
inline fun <T : StateRecord, R> T.withCurrent(block: (r: T) -> R): R =
block(current(this))
@PublishedApi
internal fun <T : StateRecord> current(r: T) =
Snapshot.current.let { snapshot ->
readable(r, snapshot.id, snapshot.invalid) ?: sync {
Snapshot.current.let { syncSnapshot ->
readable(r, syncSnapshot.id, syncSnapshot.invalid)
}
} ?: readError()
}
value 的 setter 首先是调用了三参数的 readable(),上面有提到三参数的 readable() 就是获取一个最新的、可用的 StateRecord。policy.equivalent() 先判断数值是否改变了,如果有就执行 next.overwritable()。
Snapshot.kt
internal inline fun <T : StateRecord, R> T.overwritable(
state: StateObject, // SnapshotMutableStateImpl
candidate: T, // 最新的可用的 StateRecord
block: T.() -> R // block 处理的赋值操作 this.value = value
): R {
var snapshot: Snapshot = snapshotInitializer
return sync {
// 获取对应当前 Snapshot 的 StateRecord
// overwritableRecord() 返回的是 T
// T 是 SnapshotMutableStateImpl 的 next 变量
// 所以 overwritableRecord().block() 是调用 overwritable() 的传参 block
// block 这个 lambda 就是修改了值
snapshot = Snapshot.current
this.overwritableRecord(state, snapshot, candidate).block()
}.also {
// 找到读取的 StateRecord 标记为失效,在下一帧重新刷新
notifyWrite(snapshot, state)
}
}
next.overwritable() 会调用 overwritableRecord() 会返回一个 StateRecord,然后调用 notifyWrite()。我们先看 overwritableRecord():
internal fun <T : StateRecord> T.overwritableRecord(
state: StateObject,
snapshot: Snapshot,
candidate: T
): T {
if (snapshot.readOnly) {
// If the snapshot is read-only, use the snapshot recordModified to report it.
snapshot.recordModified(state)
}
val id = snapshot.id
if (candidate.snapshotId == id) return candidate
val newData = newOverwritableRecord(state, snapshot)
newData.snapshotId = id
snapshot.recordModified(state)
return newData
}
overwritableRecord() 的代码具体可以分成两部分看:
// 如果传参的 StateRecord 的 snapshotId 和 snapshot 的 id 一样就直接返回
val id = snapshot.id
if (candidate.snapshotId == id) return candidate
candidate 是通过三参数 readable() 获取的最新的可用的 StateRecord,在上一节点我们提及,每个 StateRecord 都有对应的 snapshotId,如果和当前 snapshotId 一样,说明这个 StateRecord 是可用的可以直接返回。
// snapshotId 不一样,返回一个新的可用的 StateRecord
// 这个 StateRecord 可能是用已有的,也有可能是新创建的
// StateRecord 和 Snapshot 就算不直接对应
// 只要 StateRecord 的 Snapshot 对另一个是有效的,另一个就能取到这个 StateRecord
val newData = newOverwritableRecord(state, snapshot)
newData.snapshotId = id
snapshot.recordModified(state)
当前的 Snapshot 是有可能没有相应的 StateRecord,因为 Snapshot 是有先后关系且之前的 Snapshot 的 StateRecord 对当前 Snapshot 是有效的,即使 StateRecord 和 Snapshot 不直接对应,也是可以返回这个 StateRecord。
@PublishedApi
internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
snapshot.writeObserver?.invoke(state) // 通知订阅的位置标记为失效
}
最后是调用 notifyWrite(),其实就是将调用 value 的 getter 标记为失效,要在下一帧刷新。
总结下 setter 的操作主要有两个步骤:
-
overwritable() 调用 overwritableRecord() 获取当前 Snapshot 可用的 StateRecord,然后更新 StateRecord 的 value
-
通知订阅的位置标记为失效,下一帧时刷新
Compose 的两套订阅机制
上面我们了解了 MutableState 的原理,简单说就是 getter 负责订阅,setter 负责通知标记失效,等下一帧刷新。
但实际上这还并不是完整的原理,刚刚我们讲到的订阅只是其中一个订阅步骤。实际上 Compose 是有两个订阅过程的:
Snapshot.kt
fun <T : StateRecord> T.readable(state: StateObject): T {
val snapshot = Snapshot.current
snapshot.readObserver?.invoke(state)
return readable(this, snapshot.id, snapshot.invalid) ?: sync {
val syncSnapshot = Snapshot.current
readable(this, syncSnapshot.id, syncSnapshot.invalid)
} ?: readError()
}
@PublishedApi
internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
snapshot.writeObserver?.invoke(state)
}
Snapshot 分别使用了 readObserver 和 writeObserver,看名字就能知道 Observer 它是属于接收者,是属于订阅机制里面的被通知的对象。
实际上 readObserver 和 writeObserver 它们都属于两个不同订阅机制中的两个被通知的对象,即 readObserver 是对读进行订阅,writeObserver 是对写进行订阅,分别收到读和写的通知。所以这也侧面说明 Compose 是有两套订阅的。
Compose 中有两个订阅过程:
-
第一个订阅发生在 Snapshot 创建的时候开始订阅,这里订阅的是 Snapshot 对读写 StateObject 对象的订阅。当 Snapshot 内部读写 StateObject 对象时,readObserver 和 writeObserver 就会分别收到相应的通知,通知某个 StateObject 被读写了
-
第二个订阅是对每个 StateObject 的应用事件做订阅
应用事件就是让这个新值变成全局可用的,Compose 允许存在多个 Snapshot,但只能存在一个当前且全局的 Snapshot,当对一个非全局的 Snapshot 的改动应用到全局的 Snapshot 时,这个改动才算是生效的,这个过程就是所谓的应用。
第二个订阅发生在 readObserver 被调用的时候,也就是调用 snapshot.readObserver?.invoke(state) 时,这个调用就是应用事件的订阅。通知发生在 StateObject 新值被应用的时候。
所以 snapshot.readObserver?.invoke(state) 既是通知又是对应用事件的订阅。
// 既是对 Snapshot 订阅的读的通知
// 也是做 StateObject 新值的应用事件的订阅
snapshot.readObserver?.invoke(state)
在上面我们讲到,在变量写的时候会通过 writeObserver 把应用到变量的地方标记为失效,在下一帧刷新,其实这只是其中一种情况而已。writeObserver 订阅的是 Snapshot,而 Snapshot 主要用在 Compose 过程也就是用在组合过程,也就是说 writeObserver 是在组合过程中发生了变量值的改变,这时候它才会去标记失效:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val test = mutableStateOf("1")
setContent {
Box(modifier = Modifier.clickable {
// 没有发生在 Compose 过程,发生的写事件是不会被通知到的
// 只有发生 Compose 组合过程中的写事件才会通知 writeObserver
test.value = "2"
}) {
// test.value 触发读过程并且记录了
Text(test.value)
// 触发写,会让 Text(test.value) 失效
// Text() 所在的 lambda 会在下一帧 Recompose
test.value = "3"
}
}
}
}
上面例子中 Box() 的点击事件因为没有参与组合过程,所以在点击事件对 MutableState 写操作并不会触发 Recompose。
by 委托便捷写法
每次使用 MutableState 都要 .value 才能使用很麻烦,所以 Compose 为我们提供了简洁处理,帮我们去掉了这个操作,通过 by 将 setValue 和 getValue 都交由 mutableStateOf 委托处理。
// val name = mutableStateOf("name")
var name by mutableStateOf("name")
setContent {
// Text(name.value)
Text(name)
}
Compose 引入了一个扩展函数 androidx.compose.runtime.setValue 和 androidx.compose.runtime.getValue,将 MutableState 类型转换为调用的数值类型,但目前仍需要手动导入这个扩展。
总结
简单总结下 mutableStateOf 的读写原理:
-
mutableStateOf 的 value 在 getter 时,Compose 会把它读的位置记录下来
-
value 的 setter 会将读到这个变量的所有地方标记为失效
-
当每个新值被应用的时候,Compose 也会把读到这个变量的所有地方标记为失效,标记失效的地方都会在下一帧进行刷新