Kotlin 协程 提供一个 API,使您能够编写异步代码。使用 Kotlin 协程,您可以定义一个 CoroutineScope
,它可以帮助您管理协程应该运行的时间。每个异步操作都在特定范围内运行。
生命周期感知组件 为应用中的逻辑范围提供协程的一流支持,以及与 LiveData
的互操作性层。本主题解释了如何将协程与生命周期感知组件有效地结合使用。
添加 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
属性访问 ViewModel
的 CoroutineScope
,如下面的示例所示。
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
LifecycleScope
每个 Lifecycle
对象都定义了一个 LifecycleScope
。在此范围内启动的任何协程将在 Lifecycle
被销毁时取消。您可以通过 lifecycle.coroutineScope
或 lifecycleOwner.lifecycleScope
属性访问 Lifecycle
的 CoroutineScope
。
以下示例演示了如何使用 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
时取消收集。这种方法仅在 UI 在屏幕上可见时处理流发射,从而节省资源并可能避免应用程序崩溃。
对于这些情况,Lifecycle
和 LifecycleOwner
提供了暂停的 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 {
flow1.collect { /* Process the value. */ }
}
launch {
flow2.collect { /* Process the value. */ }
}
}
}
暂停生命周期感知协程
即使 CoroutineScope
提供了一种在 Lifecycle
被 DESTROYED
时自动取消长时间运行操作的正确方法,您可能还有其他情况,您希望在 Lifecycle
处于特定状态时暂停代码块的执行。例如,要运行 FragmentTransaction
,您必须等到 Lifecycle
至少处于 STARTED
状态。对于这些情况,Lifecycle
提供了其他方法:lifecycle.whenCreated
、lifecycle.whenStarted
和 lifecycle.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
被销毁,则协程将自动取消。在下面的示例中,finally
块将在 Lifecycle
状态变为 DESTROYED
时运行。
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
时,您可能需要异步计算值。例如,您可能希望检索用户的偏好并将它们提供给您的 UI。在这些情况下,您可以使用 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))
}
}
您还可以将 liveData
与 Transformations
组合,如下面的示例所示。
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
您可以通过在想要发射新值时调用 emitSource()
函数来从 LiveData
发射多个值。请注意,每次调用 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)
}
)
}
}
}
有关更多协程相关信息,请参阅以下链接。
其他资源
要了解有关将协程与生命周期感知组件一起使用的更多信息,请查阅以下其他资源。
示例
博客
为您推荐
- 注意:JavaScript 关闭时显示链接文本
- LiveData 概述
- 使用生命周期感知组件处理生命周期
- 加载和显示分页数据