持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
Compose中的状态
在Compose
中,可组合函数读取的任何状态都应该由一个特殊的对象支撑,该对象都应该由以下函数返回:
mutableStateOf
/MutableState
mutableStateListOf
/SnapshotStateList
mutableStateMapOf
/SnapshotStateMap
derivedStateOf
rememberUpdatedState
collectAsState
我们都知道在Compose
状态发生改变Compose
可组合函数就会发生重组,那么Compose
底层的状态管理和重组机制是怎么发生的?
快照
在Compose
中定义了一个Snapshot
快照类型并且一些处理快照的Api。快照类似于我们游戏中的存档点,只是这个存档是针对Compose
中的状态值进行存档。快照这个概念并不是只存在Compose
中,我们平时使用的Git
分支管理系统和这个概念也有点类似。
下面我们通过一个简单的例子了解一下快照
的创建
@Composable
fun TestScreen() {
val viewModel: CanteenViewModel = viewModel()
val activity = LocalContext.current as Activity
//获取当前所有状态值的快照
val snapShot = Snapshot.takeSnapshot()
viewModel.name.value = "Compose"
DisposableEffect(key1 = Unit){
onDispose {
//当快照完成时,必须要销毁它
snapShot.dispose()
}
}
snapShot.enter {
//enter->lambda中可以将状态值恢复到创建快照那个时间点时候的值并且应用到block范围内
Toast.makeText(activity, viewModel.name.value, Toast.LENGTH_LONG).show()
}
Box(
modifier = Modifier
.fillMaxSize()
.noPressColorClick {
}
.background(whiteFFFFFFFF),
contentAlignment = Alignment.Center
) {
Text(text = viewModel.name.value)
}
}
复制代码
takeSnapshot()
会获取当前所有的状态值,,无论它们是在哪里创建或设置的。enter
会将函数将状态的快照恢复并且在block中能获取
enter()
只能读取快照中的状态值,并不能对状态值进行修改,如果修改状态值就会抛出IllegalStateException
,需要修改状态的就需要使用takeMutableSnapshot()
,它和takeSnapShot()
一样会获取当前所有状态值的快照,只是使用它创建的快照,在enter()
block中可以修改状态值
@Composable
fun TestScreen() {
val viewModel: CanteenViewModel = viewModel()
val activity = LocalContext.current as Activity
//获取当前所有状态值的快照
val snapShot = Snapshot.takeMutableSnapshot()
DisposableEffect(key1 = Unit){
onDispose {
//当快照完成时,必须要销毁它
snapShot.dispose()
}
}
snapShot.enter {
//enter->lambda中可以将状态值恢复到创建快照那个时间点时候的值并且应用到block范围内
viewModel.name.value = "Compose"
Toast.makeText(activity, viewModel.name.value, Toast.LENGTH_LONG).show()
}
Box(
modifier = Modifier
.fillMaxSize()
.background(whiteFFFFFFFF),
contentAlignment = Alignment.Center
) {
Text(text = viewModel.name.value)
}
}
复制代码
可以看出在enter
修改的状态值并没有在block范围之外生效,这个是快照中很重的一个隔离机制,如果需要让修改的状态值生效,需要调用snapShot.apply()
方法去应用快照中状态的变更。如果觉得这个操作很繁琐,那么withMutableSnapShot()
就帮我们简化了这个操作。
状态的读写观察者
既然知道状态管理是通过观察者模式实现的,那么状态管理中的观察者又是谁?从MutableSnapshot
源码,可以看出定义了readObserver
和writeObserver
,这两个Lambda就是读写操作时的回调。
open class MutableSnapshot internal constructor(
id: Int,
invalid: SnapshotIdSet,
override val readObserver: ((Any) -> Unit)?,
override val writeObserver: ((Any) -> Unit)?
) : Snapshot(id, invalid) {
/****/
}
复制代码
@Composable
fun TestScreen() {
val viewModel: CanteenViewModel = viewModel()
val readObserver: (Any) -> Unit = { readState ->
Log.d("readObserver", "$readState")
}
val writeObserver: (Any) -> Unit = { writeState ->
Log.d("writeObserver", "$writeState")
}
//获取当前所有状态值的快照
val snapShot = Snapshot.takeMutableSnapshot(readObserver, writeObserver)
DisposableEffect(key1 = Unit) {
onDispose {
//当快照完成时,必须要销毁它
snapShot.dispose()
}
}
snapShot.enter {
//enter->lambda中可以将状态值恢复到创建快照那个时间点时候的值并且应用到block范围内
viewModel.name.value = "Compose" //触发writeObserver
println(viewModel.name.value) //触发readObserver
}
snapShot.apply()
Box(
modifier = Modifier
.fillMaxSize()
.background(whiteFFFFFFFF),
contentAlignment = Alignment.Center
) {
Text(text = viewModel.name.value)
}
}
复制代码
Log输出:

D/writeObserver: MutableState(value=Compose)@101299502
D/readObserver: MutableState(value=Compose)@101299502
复制代码
全局快照
全局快照是位于快照树根的可变快照,与所有其他快照不同,全局快照在状态的修改上不需要去进行apply
操作。全局快照的状态变化往往来源于子快照的提交发生的。这有三种方式推进全局快照:
- 应用可变快照,自己快照的
apply
操作会应用于全局快照,并且全局快照时高级的 - 调用
Snapshot.notifyObjectsInitialized
,会向自上次推进以来更改的任何状态值发送通知 Snapshot.sendApplyNotifycations()
.这个方法类似于notifyObjectsInitialized
,但是只有在实际发生更改时才会推进快照。在第一种情况下,只要将任何可变快照应用于全局快照,就会隐式调用此函数。
@Composable
fun TestScreen() {
val viewModel: CanteenViewModel = viewModel()
//获取当前所有状态值的快照
val snapShot = Snapshot.takeMutableSnapshot(readObserver, writeObserver)
DisposableEffect(key1 = Unit) {
onDispose {
//当快照完成时,必须要销毁它
snapShot.dispose()
}
}
snapShot.registerApplyObserver { changedSet, snapshot ->
if (viewModel.name.value in changedSet) Log.d("registerApplyObserver", "${viewModel.name.value}")
}
viewModel.name = "Compose"
snapShot.sendApplyNotifications()
Box(
modifier = Modifier
.fillMaxSize()
.background(whiteFFFFFFFF),
contentAlignment = Alignment.Center
) {
Text(text = viewModel.name.value)
}
}
复制代码