Compose的快照系统

持续创作,加速成长!这是我参与「掘金日新计划 · 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中能获取

image.png

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)
    }

}
复制代码

image.png

可以看出在enter修改的状态值并没有在block范围之外生效,这个是快照中很重的一个隔离机制,如果需要让修改的状态值生效,需要调用snapShot.apply()方法去应用快照中状态的变更。如果觉得这个操作很繁琐,那么withMutableSnapShot()就帮我们简化了这个操作。

状态的读写观察者

既然知道状态管理是通过观察者模式实现的,那么状态管理中的观察者又是谁?从MutableSnapshot源码,可以看出定义了readObserverwriteObserver,这两个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)
    }
}
复制代码

猜你喜欢

转载自juejin.im/post/7109101643798577182