一、前言
借用官方的回答 由于 Compose 是声明式工具集,因此更新它的唯一方法是通过新参数调用同一可组合项。这些参数是界面状态的表现形式。每当状态更新时,都会发生重组。 不会像在基于 XML 的命令式视图中那样自动更新
。可组合项必须明确获知新状态,才能相应地进行更新
,而获取新状态使用的就是State
。
1.1、传统思维
什么意思呢可以看一段代码: 可以测试到clickable更改value的值变化,文本的值并不会发生改变
,虽然状态变化但是没有发生重组。要想根据状态发生重组可以使用MutableState
API 将对象存储在内存中。系统会在初始组合期间将由 remember
包裹记忆更改的MutableState
值,并在重组期间返回存储的值。remember
既可用于存储可变对象,又可用于存储不可变对象。
kotlin
@Composable
private fun testValueChange() {
var value=1
Text(text = "测试传统值改变:$value", modifier = Modifier
.padding(vertical = 6.dp)
.background(Color.Blue)
.padding(6.dp)
.clickable {
value ++
})
}
1.2、使用可组合项更新
如果 countRemember
有任何变化,系统就会为用于读取 countRemember
的所有可组合函数安排重组。
在可组合项中声明 MutableState
对象的方法有三种:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
这些声明是等效的,以语法糖的形式针对状态的不同用法提供。
kotlin
@Composable
private fun testRemember() {
var countRemember = remember { mutableStateOf(0) }
Log.d("tgw", "testRemember: ")
Text(text = "测试remember的更新方式--${countRemember.value}", modifier = Modifier
.padding(vertical = 6.dp)
.background(Color.Blue)
.padding(6.dp)
.clickable {
countRemember.value = countRemember.value + 1
})
}
注意下面两点问题,后续将给出原因与解决方法:
remember
会将对象存储在组合中,当调用remember
的可组合项从组合中移除后,它会忘记该对象
,组合发生移除重组后,会造成值丢失或不正确等一些不可控的情况出现
,后面重点要讲的。- 虽然
remember
可帮助您在重组后保持状态,但不会帮助您在配置更改后保持状态。为此,您必须使用rememberSaveable
。rememberSaveable
会自动保存可保存在Bundle
中的任何值。对于其他值,您可以将其传入自定义 Saver 对象。
二、remember使用不当造成的状态忘记
2.1、什么叫状态忘记
状态忘记估计初次见到也会一脸懵逼,看一个例子解释一下。
1.先关注 testRemember
方法观察现象,testLiveData是对于问题的解决
。 我们一般很容易就写出这样子的代码,写了一个Column
然后根据一些条件动态的显示
(这里是用了一个点击事件让当前的GreetingMain函数发生重组)布局A(testRemember)
,或布局B(testLiveData)
。
2.如果单纯的看布局A或B
的显示隐藏当然没问题,可一旦(布局A)testRemember函数中
使用了一些状态记录(如:countRemember更改它的值,使用Text进行显示)这样的话,GreetingMain函数的重组就会导致,布局A从显示到移除再到显示之后(布局A)testRemember函数中
之前修改的countRemember值丢失掉
如下图:测试remember的更新方式更改的值,在重组后丢失。
scss
@Composable
fun GreetingMain(name: String) {
var parentUpdate = remember { mutableStateOf(0) }
Column {
Log.d("tgw", "GreetingMain:开始测试")
if (parentUpdate.value == 0) {
testRemember()
testLiveData()
}else if (parentUpdate.value % 2 == 0){
testLiveData(parentUpdate.value) //布局B
}else if (parentUpdate.value % 2 != 0){
testRemember() //布局A
}
Button(onClick = {
//更改条件值
parentUpdate.value= parentUpdate.value + 1
}) {
Text(
text = "点击开始测试父类重组${parentUpdate.value}", modifier = Modifier
.padding(vertical = 6.dp)
.background(Color.Blue)
.padding(6.dp)
)
}
}
}
布局A:
kotlin
@Composable
private fun testRemember() {
var countRemember = remember { mutableStateOf(0) }
Log.d("tgw", "testRemember: ")
Text(text = "测试remember的更新方式--${countRemember.value}", modifier = Modifier
.padding(vertical = 6.dp)
.background(Color.Blue)
.padding(6.dp)
.clickable {
countRemember.value = countRemember.value + 1
})
}
2.2、如何解决状态丢失
- :使用
rememberSaveable
进行初始化 - :
在ViewModel remember 初始化状态
其实主要就是延长所需要保存的值,使其生命周期较长
使用后效果如下图:
rememberSaveable
进行初始化如下
kotlin
@Composable
private fun testRemember() {
var countRemember = rememberSaveable { mutableStateOf(0) }
Log.d("tgw", "testRemember: ")
Text(text = "测试remember的更新方式--${countRemember.value}", modifier = Modifier
.padding(vertical = 6.dp)
.background(Color.Blue)
.padding(6.dp)
.clickable {
countRemember.value = countRemember.value + 1
})
}
- 在ViewModel 中使用observeAsState的状态在重组后不丢失,它的状态不丢失最关键的是值的初始化在另一个类里面如:viewmodel(
状态所有者,管理保存数据状态
),意味着在viewmodel中使用 var parentUpdate = remember { mutableStateOf(0) }也可以防止数据状态丢失。
要想使用的LiveData的observeAsState
得加入依赖
scss
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.3.2")
}
创建viewmodel
kotlin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
/**
*@Author tgw
*@Date 2023/07/06
*@describe
*/
class MainViewModel:ViewModel() {
val count = MutableLiveData<Int>(0)
fun add() {
count.value = count.value?.plus(1)
}
}
kotlin
//创建一个viewModel
private val mainViewModel : MainViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
@Composable
private fun testLiveData() {
var value=1
var countLiveData = mainViewModel.count.observeAsState()
Log.d("tgw", "testLiveData: ")
Text(text = "测试LiveData的更新方式传统值:$value--${countLiveData.value}", modifier = Modifier
.padding(vertical = 6.dp)
.background(Color.Blue)
.padding(6.dp)
.clickable {
mainViewModel.add()
value = value ++
})
Log.d("tgw", "testLiveData:LocalLifecycleOwner current ${LocalLifecycleOwner.current}")
Log.d("tgw", "testLiveData:countLiveData ${countLiveData}")
LaunchedEffect(Unit){
Log.d("tgw", "testLiveData:LaunchedEffect ")
}
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(Unit ){
Log.d("tgw", "testLiveData:DisposableEffect ")
val observer = LifecycleEventObserver { source, event ->
when (event) { //根据Event执行不同生命周期的操作
Lifecycle.Event.ON_CREATE -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_CREATE")
}
Lifecycle.Event.ON_RESUME -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_RESUME")
}
Lifecycle.Event.ON_PAUSE -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_PAUSE")
}
Lifecycle.Event.ON_DESTROY -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_DESTROY")
}
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose{
Log.d("tgw", "testLiveData:DisposableEffect onDispose ")
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
当调用
remember 的可组合项从组合中移除后,它会忘记该对象,可以认为重新添加的话将是一个新的对象
,我们可以从函数的生命周期观测到。
除了Livedata与remember,当然还有flow相关的 使用flow.collectAsState方法转化为可观察状态的对象
,相当于在viewmodel中初始化了flow在compose中使用collectAsState方法转化。 而关于flow的使用可以看我的另一篇文章: Kotlin中 Flow、SharedFlow与StateFlow区别
snapshotFlow(compose独有)
MutableStateFlow
侧重状态---mainViewModel.sharedFlow.collectAsState(initial = 1)
MutableSharedFlow
侧重事件---mainViewModel.stateFlow.collectAsState(initial = 1)
2.3、解析LiveData的observeAsState,与flow的collectAsState
为什么observeAsState
,与flow的collectAsState
能够根据数据变化更新状态,跟一下源码可知晓:
1.LiveData的observeAsState
,内部也是取了livedata的值构建了一个remember
对象,然后利用DisposableEffect
对数据进行观察修改
kotlin
@Composable
fun <T> LiveData<T>.observeAsState(): State<T?> = observeAsState(value)
kotlin
@Composable
fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
val lifecycleOwner = LocalLifecycleOwner.current
val state = remember { mutableStateOf(initial) }
DisposableEffect(this, lifecycleOwner) {
val observer = Observer<T> { state.value = it }
observe(lifecycleOwner, observer)
onDispose { removeObserver(observer) }
}
return state
}
2.flow的collectAsState
less
@Suppress("StateFlowValueCalledInComposition")
@Composable
fun <T> StateFlow<T>.collectAsState(
context: CoroutineContext = EmptyCoroutineContext
): State<T> = collectAsState(value, context)
kotlin
@Composable
fun <T : R, R> Flow<T>.collectAsState(
initial: R,
context: CoroutineContext = EmptyCoroutineContext
): State<R> = produceState(initial, this, context) {
if (context == EmptyCoroutineContext) {
collect { value = it }
} else withContext(context) {
collect { value = it }
}
}
关注produceState方法(生产状态)
kotlin
@Composable
fun <T> produceState(
initialValue: T,
key1: Any?,
key2: Any?,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = remember { mutableStateOf(initialValue) }
LaunchedEffect(key1, key2) {
ProduceStateScopeImpl(result, coroutineContext).producer()
}
return result
}
结论:compose其实都是通过remember
进行数据状态更改
三、观测compose函数的生命周期
3.1、DisposableEffect
要观测生命周期可在函数中添加DisposableEffect
使用,在函数从组合项中移除的时候会回调onDispose
方法,LocalLifecycleOwner.current
的话就是为当前函数绑定activity的周期。点击重组按钮testLiveData
与testRemember
一个添加一个移除刚好符合下图日志 而LaunchedEffect
会开启一个协成,他自动与当前函数的生命周期绑定。他俩都可以接受参数,只要参数发生了变化就会再次触发执行
。
kotlin
@Composable
private fun testLiveData() {
var value=1
var countLiveData = mainViewModel.count.observeAsState()
Log.d("tgw", "testLiveData: ")
Text(text = "测试LiveData的更新方式传统值:$value--${countLiveData.value}", modifier = Modifier
.padding(vertical = 6.dp)
.background(Color.Blue)
.padding(6.dp)
.clickable {
mainViewModel.add()
value = value ++
})
Log.d("tgw", "testLiveData:LocalLifecycleOwner current ${LocalLifecycleOwner.current}")
Log.d("tgw", "testLiveData:countLiveData ${countLiveData}")
LaunchedEffect(Unit){
delay(3000)
Log.d("tgw", "testLiveData:LaunchedEffect ")
}
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(Unit ){
Log.d("tgw", "testLiveData:DisposableEffect ")
val observer = LifecycleEventObserver { source, event ->
when (event) { //根据Event执行不同生命周期的操作
Lifecycle.Event.ON_CREATE -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_CREATE")
}
Lifecycle.Event.ON_RESUME -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_RESUME")
}
Lifecycle.Event.ON_PAUSE -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_PAUSE")
}
Lifecycle.Event.ON_DESTROY -> {
Log.d("tgw", "testLiveData:DisposableEffect ON_DESTROY")
}
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose{
Log.d("tgw", "testLiveData:DisposableEffect onDispose ")
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
3.2、rememberCoroutineScope:获取组合感知作用域,以便在可组合项外启动协程
less
/**
*自动取消协成
*/
@Composable
fun MoviesScreen() {
// Creates a CoroutineScope bound to the MoviesScreen's lifecycle
val scope = rememberCoroutineScope()
val rememberSaveable = rememberSaveable{ mutableStateOf("初始化") }
Button(
onClick = {
//不会取消
lifecycleScope.launch{
delay(3000)
Toast.makeText(this@ObserveDataActivity,"普通的 不会取消协程的",Toast.LENGTH_SHORT).show()
}
// 会取消
scope.launch {
delay(3000)
Toast.makeText(this@ObserveDataActivity,"点击事件延迟三秒",Toast.LENGTH_SHORT).show()
}
}
) {
Text("rememberCoroutineScope ,开启的协成自动取消")
}
}
假设MoviesScreen
函数在协程响应之前,就因为状态变化而被移除,那么lifecycleScope.launch
还会响应,而rememberCoroutineScope
无法响应toast,因为被自动注销了。
到这里主要讲了一些数据状态更新以及监听函数的重组,和函数绑定activity的生命周期 后面将讲一下状态提升以及状态恢复
参考: developer.android.google.cn/jetpack/com...