将 Kotlin 协程与生命周期感知型组件一起使用

Kotlin 协程提供了一个可供您编写异步代码的 API。通过 Kotlin 协程,您可以定义 CoroutineScope,以帮助您管理何时应运行协程。每个异步操作都在特定范围内运行。

生命周期感知型组件针对应用中的逻辑范围为协程提供了一流的支持。本文档介绍了如何有效地结合使用协程与生命周期感知型组件。

添加依赖项

本主题中介绍的内置协程范围包含在 Lifecycle API 中。请务必在使用这些作用域时添加相应的依赖项。

  • 对于 Compose 中的 ViewModel 实用程序,请使用 implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
  • 如需在 Compose 中使用生命周期实用程序,请使用 implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version")

生命周期感知型协程范围

Compose 和 Lifecycle 库提供了以下内置范围,您可以在应用中使用这些范围。

ViewModelScope

应用中的每个 ViewModel 都定义了 ViewModelScope。如果 ViewModel 已清除,则在此范围内启动的协程都会自动取消。如果您具有仅在 ViewModel 处于活动状态时才需要完成的工作,此时协程非常有用。例如,如果要为布局计算某些数据,则应将工作范围限定至 ViewModel,以便在 ViewModel 清除后,系统会自动取消工作以避免消耗资源。

您可以通过 ViewModelviewModelScope 属性访问 ViewModelCoroutineScope,如以下示例所示:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

对于更高级的用例,您可以将自定义 CoroutineScope 直接传递到 ViewModel 的构造函数中,以替换默认的 viewModelScope。这种方法可提供更高的控制性和灵活性,尤其适用于以下情况:

  • 测试:它允许您注入 TestScope,从而更轻松地控制时间并验证单元测试中的协程行为。

  • 自定义配置:您可以在 ViewModel 开始工作之前,使用特定的 CoroutineDispatcher(例如 Dispatchers.Default,用于繁重的计算任务)或自定义 CoroutineExceptionHandler 配置范围。

与组合相关的范围

动画、网络调用或计时器等副作用必须限定在可组合函数的生命周期内。这样一来,当可组合项离开屏幕(退出组合)时,任何正在运行的协程都会自动取消,以防止内存泄漏。

Compose 提供了 LaunchedEffect API,用于以声明方式处理组合范围。

LaunchedEffect 会创建一个 CoroutineScope,让您可以运行挂起函数。该作用域与可组合函数的组合生命周期相关联,而不是与宿主 Activity 的 Lifecycle 相关联。

  • 进入:可组合项进入组合时,协程开始运行。
  • 退出:当可组合函数退出组合时,协程会被取消。
  • 重新启动:如果传递给 LaunchedEffect 的任何键发生变化,系统会取消现有协程并启动新的协程。

以下示例演示了如何使用 LaunchedEffect 创建脉冲动画。协程与可组合项在组合中的存在情况相关联,并对配置变更做出响应:

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableLongStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

如需详细了解 LaunchedEffect,请参阅 Compose 中的附带效应

生命周期感知型数据流收集

如需在 Jetpack Compose 中安全地收集流,请使用 collectAsStateWithLifecycle API。此单个函数会将 Flow 转换为 Compose State 对象,并自动为您管理生命周期订阅。默认情况下,当生命周期处于 STARTED 状态时,系统会开始收集数据;当生命周期处于 STOPPED 状态时,系统会停止收集数据。如需替换此默认行为,请传入 minActiveState 参数,并指定所需的生命周期方法,例如 Lifecycle.State.RESUMED

以下示例演示了如何在可组合项中收集 ViewModel 的 StateFlow

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

并行收集多个流

在 Compose 中,您可以通过声明多个状态变量来并行收集多个 flow。由于 collectAsStateWithLifecycle 管理着自己的底层范围,因此系统会自动处理并行收集:

@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
    // Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
    val userData by viewModel.userFlow.collectAsStateWithLifecycle()
    val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()

    // ...
}

使用 Flow 异步计算值

如果您需要异步计算值,请将 StateFlowstateIn 运算符搭配使用。

以下代码段使用转换为 StateFlow 的标准 FlowWhileSubscribed(5000) 参数可让订阅在界面消失后继续保持活跃状态 5 秒,以处理配置更改。

val uiState: StateFlow<Result> = flow {
    emit(repository.fetchData())
}
.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Result.Loading
)

使用 collectAsStateWithLifecycle 将收集的值转换为 Compose State,以便界面在数据发生变化时做出反应式更新。

如需详细了解状态,请参阅状态和 Jetpack Compose

其他资源

查看内容

示例