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

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

生命周期感知型组件针对应用中的逻辑范围以及与 LiveData 的互操作层为协程提供了头等 (first-class) 支持。本主题介绍了如何有效地结合使用协程与生命周期感知型组件。

添加 KTX 依赖项

本主题中介绍的内置协程范围包含在每个相应组件的 KTX 扩展中。请务必在使用这些范围时添加相应的依赖项。

  • 对于 ViewModelScope,请使用 androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 或更高版本。
  • 对于 LifecycleScope,请使用 androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 或更高版本。
  • 对于 liveData,请使用 androidx.lifecycle:lifecycle-livedata-ktx:2.4.0 或更高版本。

生命周期感知型协程范围

生命周期感知型组件定义了以下内置范围供您在应用中使用。

ViewModelScope

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

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

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

LifecycleScope

每个 Lifecycle 对象都定义了 LifecycleScope。在此范围内启动的协程会在 Lifecycle 被销毁时取消。您可以通过 lifecycle.coroutineScopelifecycleOwner.lifecycleScope 属性访问 LifecycleCoroutineScope

以下示例演示了如何使用 lifecycleOwner.lifecycleScope 异步创建预计算文本:

class MyFragment: Fragment() {
   
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       
super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner
.lifecycleScope.launch {
           
val params = TextViewCompat.getTextMetricsParams(textView)
           
val precomputedText = withContext(Dispatchers.Default) {
               
PrecomputedTextCompat.create(longTextContent, params)
           
}
           
TextViewCompat.setPrecomputedText(textView, precomputedText)
       
}
   
}
}

可重启生命周期感知型协程

即使 lifecycleScope 提供了适当的方法以在 Lifecycle 处于 DESTROYED 状态时自动取消长时间运行的操作,但在某些情况下,您可能需要在 Lifecycle 处于某个特定状态时开始执行代码块,并在其处于其他状态时取消。例如,您可能希望在 Lifecycle 处于 STARTED 状态时收集数据流,并在其处于 STOPPED 状态时取消收集。通过此方法,应用仅在界面显示在屏幕上时才处理数据流发出操作,从而节省资源并可能避免发生应用崩溃问题。

对于这些情况,LifecycleLifecycleOwner 提供了挂起 repeatOnLifecycle API 来确切实现相应操作。以下示例中的代码块会在关联的 Lifecycle 至少处于 STARTED 状态时运行,并且会在 Lifecycle 处于 STOPPED 状态时取消运行:

class MyFragment : Fragment() {

   
val viewModel: MyViewModel by viewModel()

   
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       
super.onViewCreated(view, savedInstanceState)

       
// Create a new coroutine in the lifecycleScope
        viewLifecycleOwner
.lifecycleScope.launch {
           
// repeatOnLifecycle launches the block in a new coroutine every time the
           
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            viewLifecycleOwner
.repeatOnLifecycle(Lifecycle.State.STARTED) {
               
// Trigger the flow and start listening for values.
               
// This happens when lifecycle is STARTED and stops
               
// collecting when the lifecycle is STOPPED
                viewModel
.someDataFlow.collect {
                   
// Process item
               
}
           
}
       
}
   
}
}

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

如果您只需要对单个数据流执行生命周期感知型收集,可以使用 Flow.flowWithLifecycle() 方法简化代码:

viewLifecycleOwner.lifecycleScope.launch {
    exampleProvider
.exampleFlow()
       
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
       
.collect {
           
// Process the value.
       
}
}

但是,如果您需要并行对多个数据流执行生命周期感知型收集,则必须在不同的协程中收集每个数据流。在这种情况下,直接使用 repeatOnLifecycle() 会更加高效:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner
.repeatOnLifecycle(Lifecycle.State.STARTED) {
       
// Because collect is a suspend function, if you want to
       
// collect multiple flows in parallel, you need to do so in
       
// different coroutines.
        launch
{
            flow
1.collect { /* Process the value. */ }
       
}

        launch
{
            flow
2.collect { /* Process the value. */ }
       
}
   
}
}

挂起生命周期感知型协程

即使 CoroutineScope 提供了适当的方法来自动取消长时间运行的操作,在某些情况下,您可能需要暂停执行代码块(除非 Lifecycle 处于特定状态)。例如,如需运行 FragmentTransaction,您必须等到 Lifecycle 至少为 STARTED。对于这些情况,Lifecycle 提供了其他方法:lifecycle.whenCreatedlifecycle.whenStartedlifecycle.whenResumed。如果 Lifecycle 未至少处于所需的最低状态,则会挂起在这些块内运行的任何协程。

以下示例包含仅当关联的 Lifecycle 至少处于 STARTED 状态时才会运行的代码块:

class MyFragment: Fragment {
   
init { // Notice that we can safely launch in the constructor of the Fragment.
        lifecycleScope
.launch {
            whenStarted
{
               
// The block inside will run only when Lifecycle is at least STARTED.
               
// It will start executing when fragment is started and
               
// can call other suspend methods.
                loadingView
.visibility = View.VISIBLE
               
val canAccess = withContext(Dispatchers.IO) {
                    checkUserAccess
()
               
}

               
// When checkUserAccess returns, the next line is automatically
               
// suspended if the Lifecycle is not *at least* STARTED.
               
// We could safely run fragment transactions because we know the
               
// code won't run unless the lifecycle is at least STARTED.
                loadingView
.visibility = View.GONE
               
if (canAccess == false) {
                    findNavController
().popBackStack()
               
} else {
                    showContent
()
               
}
           
}

           
// This line runs only after the whenStarted block above has completed.

       
}
   
}
}

如果在协程处于活动状态时通过某种 when 方法销毁了 Lifecycle,协程会自动取消。在以下示例中,一旦 Lifecycle 状态变为 DESTROYEDfinally 块即会运行:

class MyFragment: Fragment {
   
init {
        lifecycleScope
.launchWhenStarted {
           
try {
               
// Call some suspend functions.
           
} finally {
               
// This line might execute after Lifecycle is DESTROYED.
               
if (lifecycle.state >= STARTED) {
                   
// Here, since we've checked, it is safe to run any
                   
// Fragment transactions.
               
}
           
}
       
}
   
}
}

将协程与 LiveData 一起使用

使用 LiveData 时,您可能需要异步计算值。例如,您可能需要检索用户的偏好设置并将其传送给界面。在这些情况下,您可以使用 liveData 构建器函数调用 suspend 函数,并将结果作为 LiveData 对象传送。

在以下示例中,loadUser() 是在其他位置声明的挂起函数。使用 liveData 构建器函数异步调用 loadUser(),然后使用 emit() 发出结果:

val user: LiveData<User> = liveData {
   
val data = database.loadUser() // loadUser is a suspend function.
    emit
(data)
}

liveData 构建块用作协程和 LiveData 之间的结构化并发基元。当 LiveData 变为活动状态时,代码块开始执行;当 LiveData 变为非活动状态时,代码块会在可配置的超时过后自动取消。如果代码块在完成前取消,则会在 LiveData 再次变为活动状态后重启;如果在上次运行中成功完成,则不会重启。请注意,代码块只有在自动取消的情况下才会重启。如果代码块由于任何其他原因(例如,抛出 CancellationException)而取消,则不会重启。

您还可以从代码块中发出多个值。每次 emit() 调用都会挂起代码块的执行,直到在主线程上设置 LiveData 值。

val user: LiveData<Result> = liveData {
    emit
(Result.loading())
   
try {
        emit
(Result.success(fetchUser()))
   
} catch(ioException: Exception) {
        emit
(Result.error(ioException))
   
}
}

您也可以将 liveDataTransformations 结合使用,如以下示例所示:

class MyViewModel: ViewModel() {
   
private val userId: LiveData<String> = MutableLiveData()
   
val user = userId.switchMap { id ->
        liveData
(context = viewModelScope.coroutineContext + Dispatchers.IO) {
            emit
(database.loadUserById(id))
       
}
   
}
}

您可以从 LiveData 中发出多个值,方法是在每次想要发出新值时调用 emitSource() 函数。请注意,每次调用 emit()emitSource() 都会移除之前添加的来源。

class UserDao: Dao {
   
@Query("SELECT * FROM User WHERE id = :id")
   
fun getUser(id: String): LiveData<User>
}

class MyRepository {
   
fun getUser(id: String) = liveData<User> {
       
val disposable = emitSource(
            userDao
.getUser(id).map {
               
Result.loading(it)
           
}
       
)
       
try {
           
val user = webservice.fetchUser(id)
           
// Stop the previous emission to avoid dispatching the updated user
           
// as `loading`.
            disposable
.dispose()
           
// Update the database.
            userDao
.insert(user)
           
// Re-establish the emission with success type.
            emitSource
(
                userDao
.getUser(id).map {
                   
Result.success(it)
               
}
           
)
       
} catch(exception: IOException) {
           
// Any call to `emit` disposes the previous one automatically so we don't
           
// need to dispose it here as we didn't get an updated value.
            emitSource
(
                userDao
.getUser(id).map {
                   
Result.error(exception, it)
               
}
           
)
       
}
   
}
}

如需更多与协程相关的信息,请参阅以下链接:

其他资源

如需详细了解如何将协程与生命周期感知型组件一起使用,请参阅下面列出的其他资源。

示例

未找到任何结果。

博客

目前没有任何推荐文档页面。

请尝试您的 Google 账号。