深入理解协程、LiveData-和-Flow(1)

class MyActivity : AppCompatActivity() {
override fun onCreate(state: Bundle?) {
super.onCreate(savedInstanceState)

lifecycleScope.launch {
// Run
}

}
}

有些时候,您可能还需要在生命周期的某个状态 (启动时/恢复时等) 执行一些操作,这时您可以使用 launchWhenStarted、launchWhenResumed、launchWhenCreated 这些方法:

class MyActivity : Activity {
override fun onCreate(state: Bundle?) {
super.onCreate(savedInstanceState)

lifecycleScope.launch {
// Run
}

lifecycleScope.launchWhenResumed {
// Run
}
}
}

注意,如果您在 launchWhenStarted 中设置了一个操作,当 Activity 被停止时,这个操作也会被暂停,直到 Activity 被恢复 (Resume)。

最后一种作用域的情况是贯穿整个应用。如果这个操作非常重要,您需要确保它一定被执行,这时请考虑使用 WorkManager。比如您编写了一个发推的应用,希望撰写的推文被发送到服务器上,那这个操作就需要使用 WorkManager 来确保执行。而如果您的操作只是清理一下本地存储,那可以考虑使用 Application Scope,因为这个操作的重要性不是很高,完全可以等到下次应用启动时再做。

WorkManager 不是本文介绍的重点,感兴趣的朋友请参考 《WorkManager 进阶课堂 | AndroidDevSummit 中文字幕视频》

接下来我们看看如何在 viewModelScope 里使用 LiveData。以前我们想在协程里做一些操作,并将结果反馈到 ViewModel 需要这么操作:

class MyViewModel : ViewModel {
private val _result = MutableLiveData()
val result: LiveData = _result

init {
viewModelScope.launch {
val computationResult = doComputation()
_result.value = computationResult
}
}
}

看看我们做了什么:

  1. 准备一个 ViewModel 私有的 MutableLiveData (MLD)
  2. 暴露一个不可变的 LiveData
  3. 启动协程,然后将其操作结果赋给 MLD

这个做法并不理想。在 LifeCycle 2.2.0 之后,同样的操作可以用更精简的方法来完成,也就是 LiveData 协程构造方法 (coroutine builder):

class MyViewModel {
val result = liveData {
emit(doComputation())
}
}

这个 liveData 协程构造方法提供了一个协程代码块,这个块就是 LiveData 的作用域,当 LiveData 被观察的时候,里面的操作就会被执行,当 LiveData 不再被使用时,里面的操作就会取消。而且该协程构造方法产生的是一个不可变的 LiveData,可以直接暴露给对应的视图使用。而 emit() 方法则用来更新 LiveData 的数据。

让我们来看另一个常见用例,比如当用户在 UI 中选中一些元素,然后将这些选中的内容显示出来。一个常见的做法是,把被选中的项目的 ID 保存在一个 MutableLiveData 里,然后运行 switchMap。现在在 switchMap 里,您也可以使用协程构造方法:

private val itemId = MutableLiveData()
val result = itemId.switchMap {
liveData { emit(fetchItem(it)) }
}

LiveData 协程构造方法还可以接收一个 Dispatcher 作为参数,这样您就可以将这个协程移至另一个线程。

liveData(Dispatchers.IO) {
}

最后,您还可以使用 emitSource() 方法从另一个 LiveData 获取更新的结果:

liveData(Dispatchers.IO) {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather())
}

接下来我们来看如何取消协程。绝大部分情况下,协程的取消操作是自动的,毕竟我们在对应的作用域里启动一个协程时,也同时明确了它会在何时被取消。但我们有必要讲一讲如何在协程内部来手动取消协程。

这里补充一个大前提: 所有 kotlin.coroutines 的 suspend 方法都是可取消的。比如这种:

suspend fun printPrimes() {
while(true) {
// Compute
delay(1000)
}
}

在上面这个无限循环里,每一个 delay 都会检查协程是否处于有效状态,一旦发现协程被取消,循环的操作也会被取消。

那问题来了,如果您在 suspend 方法里调用的是一个不可取消的方法呢?这时您需要使用 isActivate 来进行检查并手动决定是否继续执行操作:

suspend fun printPrimes() {
while(isActive) {
// Compute
}
}

LiveData 操作实践

在进入具体的操作实践环节之前,我们需要区分一下两种操作: 单次 (One-Shot) 操作和监听 (observers) 操作。比如 Twitter 的应用:

单次操作,比如获取用户头像和推文,只需要执行一次即可。 监听操作,比如界面下方的转发数和点赞数,就会持续更新数据。

让我们先看看单次操作时的内容架构:

如前所述,我们使用 LiveData 连接 View 和 ViewModel,而在 ViewModel 这里我们则使用刚刚提到的 liveData 协程构造方法来打通 LiveData 和协程,再往右就是调用 suspend 方法了。

如果我们想监听多个值的话,该如何操作呢?

第一种选择是在 ViewModel 之外也使用 LiveData:

△ Reopsitory 监听 Data Source 暴露出来的 LiveData,同时自己也暴露出 LiveData 供 ViewModel 使用

但是这种实现方式无法体现并发性,比如每次用户登出时,就需要手动取消所有的订阅。LiveData 本身的设计并不适合这种情况,这时我们就需要使用第二种选择: 使用 Flow。

ViewModel 模式

当 ViewModel 监听 LiveData,而且没有对数据进行任何转换操作时,可以直接将 dataSource 中的 LiveData 赋值给 ViewModel 暴露出来的 LiveData:

val currentWeather: LiveData =
dataSource.fetchWeather()

如果使用 Flow 的话就需要用到 liveData 协程构造方法。我们从 Flow 中使用 collect 方法获取每一个结果,然后 emit 出来给 liveData 协程构造方法使用:

val currentWeatherFlow: LiveData = liveData {
dataSource.fetchWeatherFlow().collect {
emit(it)
}
}

不过 Flow 给我们准备了更简单的写法:

val currentWeatherFlow: LiveData =
dataSource.fetchWeatherFlow().asLiveData()

接下来一个场景是,我们先发送一个一次性的结果,然后再持续发送多个数值:

val currentWeather: LiveData = liveData {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather())
}

在 Flow 中我们可以沿用上面的思路,使用 emit 和 emitSource:

val currentWeatherFlow: LiveData = liveData {
emit(LOADING_STRING)
emitSource(
dataSource.fetchWeatherFlow().asLiveData()
)
}

但同样的,这种情况 Flow 也有更直观的写法:

val currentWeatherFlow: LiveData =
dataSource.fetchWeatherFlow()
.onStart { emit(LOADING_STRING) }
.asLiveData()

接下来我们看看需要为接收到的数据做转换时的情况。

使用 LiveData 时,如果用 map 方法做转换,操作会进入主线程,这显然不是我们想要的结果。这时我们可以使用 switchMap,从而可以通过 liveData 协程构造方法获得一个 LiveData,而且 switchMap 的方法会在每次数据源 LiveData 更新时调用。而在方法体内部我们可以使用 heavyTransformation 函数进行数据转换,并发送其结果给 liveData 协程构造方法:

val currentWeatherLiveData: LiveData =
dataSource.fetchWeather().switchMap {
liveData { emit(heavyTransformation(it)) }
}

使用 Flow 的话会简单许多,直接从 dataSource 获得数据,然后调用 map 方法 (这里用的是 Flow 的 map 方法,而不是 LiveData 的),然后转化为 LiveData 即可:

val currentWeatherFlow: LiveData =
dataSource.fetchWeatherFlow()
.map { heavyTransformation(it) }
.asLiveData()

Repository 模式 Repository 一般用来进行复杂的数据转换和处理,而 LiveData 没有针对这种情况进行设计。现在通过 Flow 就可以完成各种复杂的操作:

val currentWeatherFlow: Flow =
dataSource.fetchWeatherFlow()
.map { … }
.filter { … }
.dropWhile { … }
.combine { … }
.flowOn(Dispatchers.IO)
.onCompletion { … }

数据源模式

而在涉及到数据源时,情况变得有些复杂,因为这时您可能是在和其他代码库或者远程数据源进行交互,但是您又无法控制这些数据源。这里我们分两种情况介绍:

1. 单次操作

如果使用 Retrofit 从远程数据源获取数值,直接将方法标记为 suspend 方法即可*:

suspend fun doOneShot(param: String) : String =
retrofitClient.doSomething(param)

  • Retrofit 从 2.6.0 开始支持 suspend 方法,Room 从 2.1.0 开始支持 suspend 方法。

如果您的数据源尚未支持协程,比如是一个 Java 代码库,而且使用的是回调机制。这时您可以使用 suspendCancellableCoroutine 协程构造方法,这个方法是协程和回调之间的适配器,会在内部提供一个 continuation 供开发者使用:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

Android进阶资料

以下的资料是近年来,我和一些朋友面试收集整理了很多大厂的面试真题和资料,还有来自如阿里、小米、爱奇艺等一线大厂的大牛整理的架构进阶资料。希望可以帮助到大家。

Android进阶核心笔记

百万年薪必刷面试题

最全Android进阶学习视频

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

了很多大厂的面试真题和资料,还有来自如阿里、小米、爱奇艺等一线大厂的大牛整理的架构进阶资料。希望可以帮助到大家。

Android进阶核心笔记

[外链图片转存中…(img-cYABWEIX-1712469580806)]

百万年薪必刷面试题

[外链图片转存中…(img-2p3glDS2-1712469580806)]

最全Android进阶学习视频

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值