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

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 供开发者使用:

suspend fun doOneShot(param: String) : Result =

suspendCancellableCoroutine { continuation ->

api.addOnCompleteListener { result ->

continuation.resume(result)

}.addOnFailureListener { error ->

continuation.resumeWithException(error)

}

}

如上所示,在回调方法取得结果后会调用 continuation.resume(),如果报错的话调用的则是 continuation.resumeWithException()。

注意,如果这个协程已经被取消,则 resume 调用也会被忽略。开发者可以在协程被取消时主动取消 API 请求。

2. 监听操作

如果数据源会持续发送数值的话,使用 flow 协程构造方法会很好地满足需求,比如下面这个方法就会每隔 2 秒发送一个新的天气值:

override fun fetchWeatherFlow(): Flow = flow {

var counter = 0

while(true) {

counter++

delay(2000)

emit(weatherConditions[counter % weatherConditions.size])

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
点击传送门,即可获取!

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-ANWJCs1G-1715337740127)]

【算法合集】

[外链图片转存中…(img-G67NeCwZ-1715337740128)]

【延伸Android必备知识点】

[外链图片转存中…(img-m926lwhF-1715337740128)]

【Android部分高级架构视频学习资源】

Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值