为了理解Compose的底层原理, 最近找了一些时间来阅读源码,这里记录一下自己的理解。
mutableStateOf可以声明一个状态, 当我们修改这个状态之后,所有依赖这个状态的Composable就会刷新。
val color = remember {
mutableStateOf(Color.White)
}
color.state = Color.BluemutableStateOf初始化的类是SnapshotMutableStateImpl, 它是一个State
同时也是一个StateObject。
StateObject是一个链表,firstStateRecord是一个链表的表头。
成员变量value表示它也是一个state,我们发现它的getter和setter都代理给了next,所以对于value的访问就是操作这个链表。
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>,
) : StateObjectImpl(), SnapshotMutableState<T> {
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> =
currentSnapshot().let { snapshot ->
StateStateRecord(snapshot.snapshotId, value).also {
if (snapshot !is GlobalSnapshot) {
it.next = StateStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), value)
}
}
}
override val firstStateRecord: StateRecord
get() = next
}这个链表中的元素StateRecord都会和snapshotId绑定, 根据这个snapshotid就可以做到类似隔离的事务特性。
/** Snapshot local value of a state object. */
public abstract class StateRecord(
/** The snapshot id of the snapshot in which the record was created. */
internal var snapshotId: SnapshotId
) {
internal var next: StateRecord? = null
}看个例子:
takeSnapshot可以对当前global的Snapshot做一个快照,global的Snapshot和Nest的Snapshot不会互相影响。
snapshot.enter接受一个scope, 这个scope里的代码访问到的就是Nest的Snapshot了。state虽然被赋值为1,但是这个操作是对global snapshot做的, 不会影响最NestSnapshot中取得的值。
var state by mutableStateOf<Int>(0)
val snapshot = takeSnapshot()
try {
state = 1
assertEquals(1, state)
assertEquals(0, snapshot.enter { state })
} finally {
snapshot.dispose()
}那具体是如何实现的呢?
首先takeSnapshot方法会对GlobalSnapshot做一个快照。 开始时只有一个GlobalSnapshot, id为2
GlobalSnapshot(id: 2)
当takeSnapshot后, 多了一个nestSnapshot,并且global的id增加1,状态为:
GlobalSnapshot(id: 4)
NestSnapshot(id: 3)
当给state赋值时, 访问到的是GlobalSnapshot, 会创建一个id为4的StateRecord,放在表头。链表状态为:
StateRecord(id: 4, value: 1) -> StateRecord(id: 2, value: 0)
然后这一行snapshot.enter { state },
在NestSnapshot的scope里,访问state的值。
当前的Snapshot id是3,当遍历这个链表时, 取得的值都是比当前Snapshot id小的,所以不会取到链表头,而是取得了第二个StateRecord,值为0.
再看一个例子;
var state by mutableStateOf<Int>(0)
val snapshot = takeMutableSnapshot()
try {
snapshot.enter {
state = 1
assertEquals(1, state)
}
assertEquals(0, state)
snapshot.apply().check()
assertEquals(1, state)
} finally {
snapshot.dispose()
}首先调用takeMutableSnapshot创建一个可以修改的NestSnapshot,
在NestSnapshot的Scope中对State的值进行修改, 修改后,读取state的值还是没有修改的, 当调用apply方法后, 这个修改被应用到了gloableScope。
开始时只有一个GlobalSnapshot
- GlobalSnapshot(id: 2)
当takeMutableSnapshot后, 多了一个nestSnapshot,并且global的id增加1,状态为:
-
GlobalSnapshot(id: 4)
-
NestSnapshot(id: 3)
在NestScope中给state赋值为1,该state的链表状态为;
- StateRecord(id: 3, value: 1) -> StateRecord(id: 2, value: 0)
在NestScope中读取state, 利用的snapshot ID 为3, 取到第一个state record, 值为1。
但是在scope之外读取state, 利用的是globleScope的值为4的snapshotid,但是读取到的StateRecord是id为2的元素, 值为1。 理由是 read函数都额外传入一个invalid id列表, 用来排除特定的snapshotid。
比如这个例子,创建了Snapshot id为3的Snapshot, GlobalSnapshot需要和这个NestSnapshot保持数据隔离, 所以维持了一个包含id 3的invalid ID列表, 当从GlobaleSnapshot中读取值时, 会排除id为3的StateRecord。 所以读到的仍然是0.
最后NestSnapshot调用了apply方法, 这个方法会清除invalid id列表并使globle Snapshot id加1, 这样从GlobleSnapshot中取得的就是最新的Record,也就是说NestSnapshot被应用到了GlobleSnapshot。