快照系统 (Snapshot System)
Compose 的状态管理不仅仅是 mutableStateOf 那么简单。它的底层是一个基于 MVCC (多版本并发控制) 的快照系统。
这使得 Compose 能够在后台线程中安全地修改状态,而不会阻塞主线程,并在合适的时候原子性地应用这些修改。
1. 什么是 Snapshot?
你可以把 Snapshot 想象成数据库的事务。
当 Compose 启动一帧的重组时,它会创建一个当前状态树的“快照”。
- 在快照期间读取的所有状态,都是一致的。
- 在快照期间的修改,对外界是不可见的,直到快照被“提交”。
2. 动手操作 Snapshot
我们可以手动使用 Snapshot API(通常这些是由 Compose 框架自动管理的)。
kotlin
import androidx.compose.runtime.snapshots.Snapshot
fun main() {
val state = mutableStateOf("A")
// 1. 创建快照
val snapshot = Snapshot.takeSnapshot {
// 在快照内读取,看到的是快照创建时的值
println("In snapshot: ${state.value}") // A
}
// 2. 外部修改
state.value = "B"
println("Outside: ${state.value}") // B
// 3. 再次读取快照
snapshot.enter {
println("In snapshot after external change: ${state.value}") // 依然是 A!
}
snapshot.dispose()
}这就是隔离性。重组过程可以在一个独立的快照中运行,即使其他线程正在修改数据,重组过程看到的数据也是稳定的。
3. 状态读写观察
Compose 如何知道何时重组?它拦截了状态的读取和写入。
kotlin
// 伪代码:mutableStateOf 的原理
class MutableState<T>(var value: T) {
fun get(): T {
// 1. 通知系统:有人读了我
Snapshot.current.recordRead(this)
return value
}
fun set(newValue: T) {
// 2. 通知系统:有人写了我
Snapshot.current.recordWrite(this)
value = newValue
}
}当 Snapshot.sendApplyNotifications() 被调用时,系统会对比哪些 State 发生了变化,并找到所有读取了这些 State 的 Scope (RecomposeScope),将它们标记为失效 (Invalidated)。
4. 并发与冲突
如果两个快照同时修改了同一个状态,会发生什么?
kotlin
val state = mutableStateOf(0)
val snapshot1 = Snapshot.takeMutableSnapshot()
val snapshot2 = Snapshot.takeMutableSnapshot()
snapshot1.enter { state.value += 1 }
snapshot2.enter { state.value += 2 }
snapshot1.apply()
snapshot2.apply() // 💥 冲突!抛出 SnapshotApplyConflictException默认情况下,这也是一种乐观锁机制。但我们可以定义合并策略 (MutationPolicy) 来解决冲突。
kotlin
var counter by mutableStateOf(0, policy = object : SnapshotMutationPolicy<Int> {
override fun equivalent(a: Int, b: Int): Boolean = a == b
// 自定义合并逻辑:如果冲突了,就将两个增量相加
override fun merge(previous: Int, current: Int, applied: Int): Int {
val delta = applied - previous
return current + delta
}
})