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
属性访问 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
状态时取消收集。通过此方法,应用仅在界面显示在屏幕上时才处理数据流发出操作,从而节省资源并可能避免发生应用崩溃问题。
对于这些情况,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
处于特定状态)。例如,如需运行 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
,协程会自动取消。在以下示例中,一旦 Lifecycle
状态变为 DESTROYED
,finally
块即会运行:
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))
}
}
您也可以将 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))
}
}
}
您可以从 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. The Google Home app helps set up, manage, and control your Google Home, Google Nest, and Chromecast devices—plus thousands of connected home products like lights, cameras, thermostats, and more. Duolingo is one of the most popular language learning platforms in the world, and one of the most-downloaded free education apps on Google Play, with more than 200 million downloads.Headspace's Android reboot increases monthly active users by 15%
SmartNews reduces lines of code by 20% and improves team morale with Kotlin
Twitter increases developer productivity and code reliability with Kotlin
Google Home reduces #1 cause of crashes by 33%
Duolingo completes migration to Kotlin and reduces its line count by an average of 30%
博客
目前没有任何推荐文档页面。
请尝试登录您的 Google 账号。