Jetpack Compose 中的副作用

@Composable 函数是声明式的,它们会根据输入参数来描述 UI 的样子。这些函数可能会被多次调用,例如在状态发生变化时进行重组(Recomposition)。为了保证 UI 描述的一致性和可预测性,@Composable 函数本身应该是无副作用的,也就是不会对外部状态产生影响。然而,在实际应用中,我们常常需要和外部世界进行交互,比如发起网络请求获取数据、启动和停止动画、注册和注销广播接收器等,这些操作都属于副作用。

如何使用副作用

Jetpack Compose 提供了多种副作用函数来处理不同的场景,下面介绍几个常用的副作用函数及其使用示例。

1. LaunchedEffect

LaunchedEffect 用于在 @Composable 函数中启动一个协程,适合处理异步操作,比如网络请求、数据库查询等。当 LaunchedEffect 的键(key)发生变化或者 @Composable 函数被移除时,协程会自动取消。 

@Composable
fun LaunchedEffectExample() {
    var keyValue by remember { mutableStateOf(0) }

    LaunchedEffect(null) {
       Log.v("Tag", "------keyValue = $keyValue")
    }
    Button(onClick = { keyValue++ }) {
        Text(text = "Change Key $keyValue")
    }
}

这个示例中,日志只打印了一次,Unit 作为 LaunchedEffect 的键,意味着这个协程只会在 @Composable 函数首次执行时启动一次,后续重组过程中不会重新启动。

@Composable
fun LaunchedEffectExample() {
    var keyValue by remember { mutableStateOf(0) }

    LaunchedEffect(keyValue) {
       Log.v("Tag", "------keyValue = $keyValue")
    }
    Button(onClick = { keyValue++ }) {
        Text(text = "Change Key $keyValue")
    }
}

这个示例中,点击一次就会打印一次,每次点击按钮改变 keyValue 时,LaunchedEffect 会取消当前的协程,并重新启动一个新的协程,输出新的 keyValue

2. DisposableEffect

DisposableEffect 主要用于管理资源的生命周期,在 @Composable 函数进入和离开时执行一些操作,比如注册和注销广播接收器、打开和关闭文件等。当 DisposableEffect 的键(key)发生变化或者 @Composable 函数被移除时,onDispose 块中的代码会被执行。

 

val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(Unit) {
        val observer = LifecycleEventObserver { _, event ->
            when(event){
                Lifecycle.Event.ON_CREATE -> Log.v("Tag", "onCreate")
                Lifecycle.Event.ON_START -> Log.v("Tag", "onStart")
                Lifecycle.Event.ON_RESUME -> Log.v("Tag", "onResume")
                Lifecycle.Event.ON_PAUSE -> Log.v("Tag", "onPause")
                Lifecycle.Event.ON_STOP -> Log.v("Tag", "onStop")
                Lifecycle.Event.ON_DESTROY -> Log.v("Tag", "onDestroy")
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        // 在 DisposableEffect 结束时移除 LifecycleEventObserver
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

 也可以加参数

@Composable
fun DisposableEffectWithSingleKeyExample() {
    var keyValue by remember { mutableStateOf(0) }

    DisposableEffect(keyValue) {
        Log.d("DisposableEffect", "Initializing with key: $keyValue")

        onDispose {
            Log.d("DisposableEffect", "Disposing with key: $keyValue")
        }
    }

    Button(onClick = { keyValue++ }) {
        Text(text = "Change Key ($keyValue)")
    }
}

当 keyValue 发生变化时,DisposableEffect 会先执行 onDispose 块中的代码,然后重新执行初始化代码,输出新的键值

3. SideEffect

SideEffect 用于在 @Composable 函数重组后执行一些操作,比如更新 Activity 的标题、设置状态栏颜色等。SideEffect 会在每次重组后执行。

SideEffect {
    SideEffect 会在每次重组后执行
}

4. produceState

produceState 主要用于把异步操作的结果转换为可观察的状态。当异步操作完成时,状态会更新,进而触发 @Composable 函数的重组。它适用于需要从异步源(如网络、数据库)获取数据并将其展示在界面上的场景。

val shouldDisposeBlockUpdated by rememberUpdatedState(shouldDisposeBlock)

        val shouldDisposeAfterExit by produceState(
            initialValue = shouldDisposeBlock(
                childTransition.currentState,
                childTransition.targetState
            )
        ) {
            snapshotFlow {
                childTransition.exitFinished
            }.collect {
                value = if (it) {
                    shouldDisposeBlockUpdated(
                        childTransition.currentState,
                        childTransition.targetState
                    )
                } else {
                    false
                }
            }
        }

这是 AnimatedEnterExitImpl 中的一段代码

5. derivedStateOf

derivedStateOf 用于创建一个派生状态,该状态的值依赖于其他状态。当依赖的状态发生变化时,派生状态会自动重新计算。它主要用于优化 @Composable 函数的重组,避免不必要的计算。

@Composable
fun DerivedStateOfExample() {
    var number by remember { mutableStateOf(0) }
    val squared by remember { derivedStateOf { number * number } }

    androidx.compose.material3.Button(onClick = { number++ }) {
        Text(text = "增加数字: $number")
    }

    Text(text = "数字的平方: $squared")
}

6. snapshotFlow

snapshotFlow 用于将可变状态转换为数据流(Flow)。它会监听状态的变化,并在状态发生改变时发出新的数据项。snapshotFlow 通常与协程的 collect 函数结合使用,以响应状态的变化。

@Composable
fun SnapshotFlowExample() {
    var number by remember { mutableStateOf(0) }

    LaunchedEffect(Unit) {
        snapshotFlow { number }.collect { newNumber ->
            println("数字已更新为: $newNumber")
        }
    }

    Button(onClick = { number++ }) {
        Text(text = "增加数字: $number")
    }
}

这里我们会想用下面的代码不是一样可以实现么:

@Composable
fun SnapshotFlowExample() {
    var number by remember { mutableStateOf(0) }

    LaunchedEffect(number) {
        Log.v("woody4.25", "数字已更新为: $number")
    }

    Button(onClick = { number++ }) {
        Text(text = "增加数字: $number")
    }
}

它们确实都能监听 number 的变化,但它们在实现机制、适用场景和性能表现上存在一些不同 。

实现机制

LaunchedEffect 的键参数(这里是 number)用于判断是否需要重新启动协程。当键发生变化时,当前协程会被取消,然后重新启动一个新的协程执行 LaunchedEffect 块中的代码。 

    snapshotFlow 会把可变状态(如 number)转换为一个数据流(Flow)。它会持续监听状态的变化,当状态改变时,就会发出新的数据项 。

    适用场景

    snapshotFlow 方式:

    适合需要对状态变化进行连续响应的场景,例如根据状态变化执行一系列异步操作或者需要将状态变化作为数据流进行处理的情况。

    可以和其他 Flow 操作符(如 map、filter 等)结合,实现更复杂的数据流处理逻辑。

    比如在处理输入框内容变化时,根据输入内容实时进行搜索请求,就可以使用 snapshotFlow 监听输入框内容的变化,并将其转换为搜索请求的数据流。

    LaunchedEffect(number) 方式:

    适用于状态变化时需要重新执行一些初始化或者一次性操作的场景。

    例如当某个配置项变化时,重新加载数据或者重新初始化一些资源。

    性能表现

    snapshotFlow 方式:

    由于 snapshotFlow 是持续监听状态变化,可能会有一定的性能开销,尤其是在状态频繁变化的情况下。

    不过它只会在状态真正发生变化时发出新的数据项,避免了不必要的协程重启。

    LaunchedEffect(number) 方式:

    每次状态变化时都会取消当前协程并重新启动一个新的协程,这可能会带来额外的协程创建和销毁开销。

    如果状态变化频繁,频繁的协程重启可能会影响性能。

     

    7. rememberUpdatedState

      rememberUpdatedState 用于在副作用函数中获取最新的状态值。在副作用函数里,如果直接引用状态变量,可能会捕获到旧的状态值,使用 rememberUpdatedState 可以确保获取到最新的状态值。

      @Composable
      fun RememberUpdatedStateExample() {
          var count by mutableStateOf(0)
          val currentCount by rememberUpdatedState(count)
      
          val lifecycleOwner = LocalLifecycleOwner.current
      
          LaunchedEffect(Unit) {
              val observer = LifecycleEventObserver { _, event ->
                  if (event == Lifecycle.Event.ON_RESUME) {
                      // 使用最新的 count 值
                      println("当前计数: $currentCount")
                  }
              }
              lifecycleOwner.lifecycle.addObserver(observer)
              delay(5000)
              lifecycleOwner.lifecycle.removeObserver(observer)
          }
      
          androidx.compose.material3.Button(onClick = { count++ }) {
              androidx.compose.material3.Text(text = "增加计数: $count")
          }
      }

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值