以下内容来自公众号code小生,关注每日干货及时送达f710f3d0bf5c5ebb49d4ff57fcc3b190.png

一、前言

借用官方的回答 由于 Compose 是声明式工具集,因此更新它的唯一方法是通过新参数调用同一可组合项。这些参数是界面状态的表现形式。每当状态更新时,都会发生重组。 不会像在基于 XML 的命令式视图中那样自动更新。可组合项必须明确获知新状态,才能相应地进行更新

1.1、传统思维

什么意思呢可以看一段代码: 可以测试到clickable更改value的值变化,文本的值并不会发生改变,虽然状态变化但是没有发生重组。要想根据状态发生重组可以使用remember API 将对象存储在内存中。系统会在初始组合期间将由 remember 计算的值存储在组合中,并在重组期间返回存储的值。remember 既可用于存储可变对象,又可用于存储不可变对象。

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

这些声明是等效的,以语法糖的形式针对状态的不同用法提供。

@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 可帮助您在重组后保持状态,但不会帮助您在配置更改后保持状态。为此,您必须使用 rememberSaveablerememberSaveable 会自动保存可保存在 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的更新方式更改的值,在重组后丢失。 

效果图:GIF查看下面链接:

https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2781db41f3f4492bcb261c032284472~tplv-k3u1fbpfcp-watermark.image

b6b60807660bd5f80e8ee61bdc2118db.gif

@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:

@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、如何解决状态丢失
  1. :使用rememberSaveable进行初始化

  2. 在ViewModel remember 初始化状态

使用后效果如下图: GIF查看下面链接:

https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c3d50ff87ace497d9449e36076afc302~tplv-k3u1fbpfcp-watermark.image

1fd27297318cadca0c9165e429f07f1b.gif

  • rememberSaveable进行初始化如下@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) }也可以防止数据状态丢失。安卓手机破解软件总库https://pan.quark.cn/s/c56c3947fe10

要想使用的LiveData的observeAsState得加入依赖

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-livedata:1.3.2")
}

创建viewmodel

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)
    }
}
//创建一个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区别

  1. snapshotFlow(compose独有)

  2. MutableStateFlow 侧重状态---mainViewModel.sharedFlow.collectAsState(initial = 1)

  3. MutableSharedFlow 侧重事件---mainViewModel.stateFlow.collectAsState(initial = 1)

2.3、解析LiveData的observeAsState,与flow的collectAsState

为什么observeAsState,与flow的collectAsState能够根据数据变化更新状态,跟一下源码可知晓:

1.LiveData的observeAsState,内部也是取了livedata的值构建了一个remember对象,然后利用DisposableEffect对数据进行观察修改

@Composable
fun <T> LiveData<T>.observeAsState(): State<T?> = observeAsState(value)
@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

@Suppress("StateFlowValueCalledInComposition")
@Composable
fun <T> StateFlow<T>.collectAsState(
    context: CoroutineContext = EmptyCoroutineContext
): State<T> = collectAsState(value, context)
@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方法(生产状态)

@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的周期。点击重组按钮testLiveDatatestRemember一个添加一个移除刚好符合下图日志 而LaunchedEffect会开启一个协成,他自动与当前函数的生命周期绑定。他俩都可以接受参数,只要参数发生了变化就会再次触发执行

55372cbd4467006a4d486c5d82250da0.png

b12e2407fc4f3795428514de00caffbe.png

@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:获取组合感知作用域,以便在可组合项外启动协程
/**
 *自动取消协成
 */
@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的生命周期 后面将讲一下状态提升以及状态恢复

Compose 在 Android 中的必要性和前景分析

一文速览 Compose 跨平台最新动态

c92d6d6c5e29b4d78196bdd52ee2a98b.png

GitHub 加速计划 / compose / compose
33.28 K
5.15 K
下载
compose - Docker Compose是一个用于定义和运行多容器Docker应用程序的工具,通过Compose文件格式简化应用部署过程。
最近提交(Master分支:3 个月前 )
5e3a0953 full diff: https://github.com/docker/cli/compare/v27.4.0-rc.1...8d1bacae3e49ed1d096eede8eef4ae851d7f2eae Signed-off-by: Sebastiaan van Stijn <github@gone.nl> 6 天前
a2a3eb72 - full diff: https://github.com/docker/cli/compare/cb3048fbebb1...v27.4.0-rc.1 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> 6 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐