Skip to content

快照系统 (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
    }
})