本文是我在 2019 年 Android 开发者峰会上与 Yigit Boyar 的谈话总结的第二部分。
使用协程和流的 LiveData (ADS 2019)https://youtu.be/B8ppnjGPAGEhttps://youtu.be/B8ppnjGPAGE
第二部分:使用架构组件启动协程(本文)
Jetpack 的架构组件提供了许多快捷方式,所以您不必担心任务和取消。你只需要选择你的行动的作用域:
ViewModel 的作用域
这是启动协程最常见的方法之一,因为大多数数据操作都是从ViewModel开始的。使用 viewModelScope 扩展,当 ViewModel 被清除时,任务将被自动取消。使用 viewModelScope.launch
以启动协程。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MainActivityViewModel : ViewModel {
init {
viewModelScope.launch {
// 处理事情!
}
}
}
Activity 和 Fragment 的作用域
类似地,如果您使用
,您可以将操作的作用域,限定到某个 view 的特定实例。lifecycleScope
.launch
如果你使用
, launchWhenResumed
或者 launchWhenStarted
将操作限制在某个生命周期状态,你甚至可以有一个更窄的作用域。launchWhenCreated
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MyActivity : Activity {
override fun onCreate(state: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
// 运行
}
lifecycleScope.launchWhenResumed {
// 运行
}
}
}
Application 的作用域
这儿有对于应用程序范围作用域的优秀案例(在这里阅读有关它的全部内容),但是,首先,如果您的任务最终必须执行,那么您应该考虑使用 WorkManager 。
ViewModel + LiveData
到目前为止,我们已经看到了如何启动协程,但还没有看到如何从协程接收结果。你可以像这样使用 MutableLiveData
:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// 不要这样做。使用 liveData 代替。
class MyViewModel : ViewModel() {
private val _result = MutableLiveData<String>()
val result: LiveData<String> = _result
init {
viewModelScope.launch {
val computationResult = doComputation()
_result.value = computationResult
}
}
}
但是,因为你将把这个结果暴露给你的 view,你可以通过使用
coroutine builder 来少打点字儿,它会启动协程并让你通过不可变的 LiveData 来暴露结果。使用 liveData
emit()
向它发送更新。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MyViewModel : ViewModel() {
val result = liveData {
emit(doComputation())
}
}
使用 switchMap 的 LiveData 协程构建器
在某些情况下,您希望在 LiveData 的值发生变化时启动协程。例如,在启动数据加载操作之前需要一个 ID。使用 Transformations.switchMap 有一个方便的模式:
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
private val itemId = MutableLiveData<String>()
val result = itemId.switchMap {
liveData { emit(fetchItem(it)) }
}
result
是一个不可变的 LiveData,每当 itemId
有一个新值时,它都将使用调用 fetchItem
挂起函数的结果进行更新。
从另一个 LiveData 发出所有事项
这个特性不太常见,但也可以节省一些样板:你可以使用 emitSource
传递一个 LiveData 源。当您希望先发出一个初始值,然后再发出一系列值时,这很有用。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
liveData(Dispatchers.IO) {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather())
}
取消协程
如果您使用上述任何一种模式,就不必显式地取消任务。然而,有一件重要的事儿要记住:协程取消是可协作的。
这意味着,如果调用协程被取消,您必须帮助 Kotlin 停止任务。假设您有一个启动无限循环的挂起函数。Kotlin 无法为您停止该循环,因此您需要协作,定期检查任务是否处于活动状态。您可以通过检查 isActive
属性来实现这一点。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun printPrimes() {
while(isActive) {
// 计算
}
}
顺便说一下,如果你使用 kotlinx
中的任何函数。协程(比如 delay
),你应该知道它们都是可取消的,这意味着它们会为你进行检查。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun printPrimes() {
while(true) { // 大概没问题,因为我们再里面调用了delay
// 计算
delay(1000)
}
}
尽管如此,我还是建议您添加检查,因为可能会有人在将来删除延迟调用,从而在您的代码中引入一个微妙的错误。
一次性 vs 多个值
为了理解协程(以及与此相关的响应式 UI),我们需要区分以下两种情况:
- 一次性操作:它们只运行一次并可以返回一个结果
- 返回多个值的操作:对可以随时间发出多个值的数据源的订阅。
Twitter app 显示的部分 UI 需要不同类型的操作。转发和点赞会随着时间的推移而更新。
使用了协程的一次性操作
使用挂起函数并使用 viewModelScope
或 liveData{}
调用它们是一种非常方便的运行非阻塞操作的方法。
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class MyViewModel {
val result = liveData {
emit(repository.fetchData())
}
}
然而,当我们监听变化时,事情会变得更加复杂。
使用 LiveData 接收多个值
我在 ViewModel 之外的 LiveData (2018)中讨论了这个主题,在这个文章里我讨论了可以用来解决 LiveData 从未被设计为全功能 streams 构建器这一事实的模式。
App 的 表示层(绿色) 和 数据层(蓝色) 使用 LiveData 进行通信
现在,更好的方法是使用 Kotlin 的 Flow(警告:某些部分仍处于实验阶段)。Flow 类似于 RxJava 中的响应式 streams 特性。
然而,虽然协程使非阻塞的一次性操作变得更容易,但对于 Flow 来说却不是这样。streams 仍然难以把握。但是,如果你想要创建快速且可靠的响应式 UI,我认为这是值得投入时间的。由于它是语言的一部分,并且是一个小的依赖项,许多库开始添加 Flow 支持(比如 Room)。
因此,我们可以公开来自 Data Source 和 Repository 的 Flow,而不是 LiveData,但是 ViewModel 仍然公开 LiveData,因为它是可以感知生命周期的。
在数据层使用 Flow 代替 LiveData 进行通信
接下来是 第三部分:LiveData 和协程模式