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

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

其他资源

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

示例

In order to launch new features on their Android app, Headspace spent 8 months refactoring their architecture and rewriting in Kotlin. Learn how this reboot helped their business grow.

SmartNews helps millions of people discover their world everyday by sharing timely news from a diverse set of news sources.

Twitter is one of the most widely used social media platforms where users can see what’s happening in the world at any given moment.

博客

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

请尝试您的 Google 账号。