Jetpack Compose LaunchedEffect

926 阅读4分钟

LaunchedEffect 简介

LaunchedEffect: run suspend functions in the scope of a composable

  • LaunchedEffect is executed once when entered inside the composition. And it is canceled when leaving the composition.
  • LaunchedEffect cancels/re-launch when Keys state changes
  • LaunchedEffect must have at least one key
  • LaunchedEffect Scope’s Dispatcher is Main.

当 LaunchedEffect 进入组合时,它会启动一个协程,并将代码块作为参数传递。如果 LaunchedEffect 退出组合,协程将取消。

LaunchedEffect 源码如下

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

internal class LaunchedEffectImpl(
    parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
    private val scope = CoroutineScope(parentCoroutineContext)
    private var job: Job? = null

    override fun onRemembered() {
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    }

    override fun onForgotten() {
        job?.cancel()
        job = null
    }

    override fun onAbandoned() {
        job?.cancel()
        job = null
    }
}

从源码可以看出我们传入的希望在协程中执行的 block,确实也是通过创建一个 CoroutineScope ,最终通过 launch{} 函数执行的。

通过 remember{}LaunchedEffectImpl 的配合,实现了当 key1 参数的值发生变化时,上一个 job 取消,然后重新执行 block

如需在此 Compose 函数的生命周期内仅触发一次附带效应,请将常量用作键,例如 LaunchedEffect(true) { ... }

SideEffect

Running non-suspend functions in every recomposition

  • SideEffectLaunchedEffect 的一种,功能相似
  • 每次 Compose 函数重组都会执行
  • SideEffect block 中不能运行 suspend 函数

官方文档的解释是:将 Compose 状态发布为非 Compose 代码

@Composable
fun test() {
    var timer by remember { mutableStateOf(0) }

    Box(modifier = Modifier.fillMaxSize(), 
      contentAlignment = Alignment.Center) {
      Text("Time $timer")
    }

    SideEffect {
      Thread.sleep(1000)
      timer++
    }
}

rememberUpdatedState

有时候需要在LaunchedEffect中使用最新的参数值,但是又不想重新启动LaunchedEffect,因为LaunchedEffect中包含了重量级的操作,重新启动会浪费资源,此时就需要用到rememberUpdatedState

rememberUpdatedState的作用是给某个参数创建一个引用,并保证其值被使用时是最新值。

rememberCoroutineScope

用于在 Compose 函数中创建协程 coroutine

  • rememberCoroutineScope 返回的 coroutineScope 会和其调用点的生命周期保持一致,当调用点所在的 Composition 退出时,该 coroutineScope 会被取消

rememberSaveable 状态容器

rememberSaveableremember 都是存储功能,但 rememberSaveable 存储的值在 activity 和进程重新创建后会继续保存

对于可以存储在 Bundle 内的对象,rememberSaveable 可以做所有这些工作,无需额外操作

需要告知 rememberSaveable 如何使用 Saver 保存和恢复此类的实例

Saver 描述了如何将对象转换为 Saveable 的内容,Saver 需要替换两个函数:

  • save - 将原始值转换为可保存的值
  • restore - 将保存的值转换为原始数据

DisposableEffect

官方文档解释:

For side effects that need to be cleaned up after the keys change or if the composable leaves the Composition, use DisposableEffect. If the DisposableEffect keys change, the composable needs to dispose (do the cleanup for) its current effect, and reset by calling the effect again.

中文版:

对于需要在键发生变化或可组合项退出组合后进行清理的附带效应,请使用 DisposableEffect

如果 DisposableEffect 键发生变化,可组合项需要处理(执行清理操作)其当前效应,并通过再次调用效应进行重置。

import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalLifecycleOwner

@Composable
fun rememberMapViewWithLifecycle(): MapView {
    val context = LocalContext.current
    val mapView = remember {
        MapView(context).apply {
            id = R.id.map
        }
    }

    val lifecycle = LocalLifecycleOwner.current.lifecycle
    DisposableEffect(key1 = lifecycle, key2 = mapView) {
        // Make MapView follow the current lifecycle
        val lifecycleObserver = getMapLifecycleObserver(mapView)
        lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycle.removeObserver(lifecycleObserver)
        }
    }

    return mapView
}

代码出处👆

LaunchedEffect 相似,key 值变化的时(多个 key 值任一变化),会执行 DisposableEffect

  • 先执行 DisposableEffectonDispose
  • 再执行 DisposEffect 代码块

ProduceState

普通对象数据改变无法触发 Compose 函数更新

通过 ProduceState 修饰后,成为有状态数据,数据变化会触发 Compose 函数刷新

@Composable  
fun JustLaunchEffect() {  
    var timer by remember { mutableStateOf(0) }  
  
    LaunchedEffect(key1 = Unit) {  
        delay(1000)  
        timer++  
    }  
}
@Composable  
fun JustProduceState() {  
    val timer by produceState(initialValue = 0) {  
        delay(1000)  
        value++  
    }  
}

以上两段代码效果等同

MutableStateProduceState 区别:

  • ProduceState 接收一个 lambda 表达式作为函数体,能将这些入参经过一些操作后生成 State 类型变量并返回

ProduceState 使用

  1. 创建时定义初始化值
  2. value 赋值

derivedStateOf

当您想要的某个 Compose State 衍生自另一个 State 时,会使用 derivedStateOf; 使用此函数可保证仅当计算中使用的状态之一发生变化时才会进行计算。

举例:

@Composable
fun test() {
    var a by remember {
        mutableStateOf(1)
    }
    val b = a > 0
    if (b) {
        Text("b is true")
    }
}
// 此处 b 不会触发 Compose 函数的重组

修改后

@Composable
fun test() {
    var a by remember {
        mutableStateOf(1)
    }
    val b by remember {
        derivedStateOf {
            a > 0
        }
    }
    if (b) {
        Text("b is true")
    }
}

文章参考:

Jetpack Compose Side Effects — LaunchedEffect With Example

Jetpack Compose Side Effects – With Examples