请选择 进入手机版 | 继续访问电脑版

马上加入IBC程序猿 各种源码随意下,各种教程随便看! 注册 每日签到 加入编程讨论群

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#技术求助 ASP.NET技术求助

【源码下载】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接外包项目】 面试-葵花宝典下载

官方一群:

官方二群:

如何正确的在 Android 上使用协程 ?

[复制链接]
查看1891 | 回复0 | 2019-10-22 09:05:59 | 显示全部楼层 |阅读模式

前言

你还记得是哪一年的 Google IO 正式公布 Kotlin 成为 Android 一级开辟语言吗?是 Google IO 2017 。现在两年时间过去了,站在一名 Android 开辟者的角度来看,Kotlin 的生态环境越来越好了,相关的开源项目和学习资料也日渐丰富,身边愿意去使用大概试用 Kotlin 的朋侪也变多了。常年混迹掘金的我也能明显感觉到 Kotlin 标签下的文章渐渐变多了(其实仍旧少的可怜)。本年的 Google IO 也放出了 Kotlin First 的口号,许多新的 API 和功能特性将优先提供 Kotlin 支持。所以,时至今日,实在找不到安卓开辟者不学 Kotlin 的来由了。

本日想聊聊的是 Kotlin Coroutine。虽然在 Kotlin 发布之初就有了协程,但是直到 2018 年的 KotlinConf 大会上,JetBrain 发布了 Kotlin1.3RC,这才带来了稳定版的协程。纵然稳定版的协程已经发布了一年之余,但是似乎并没有充足多的用户,至少在我看来是这样。在我学习协程的各个阶段中,碰到问题都鲜有地方可以求助,抛到技能群基本就石沉大海了。基本只能靠一些英文文档来办理问题。

关于协程的文章我看过许多,总结一下,无非下面几类。

第一类是 Medium 上热门文章的翻译,其实我也翻译过:

在 Android 上使用协程(一):Getting The Background

在 Android 上使用协程(二):Getting started

在 Android 上使用协程(三) :Real Work

说真话,这三篇文章的确加深了我对协程的理解。

第二类就是官方文档的翻译了,我看过至少不下于五个翻译版本,照旧以为看 官网文档 比力好,假如英文看着实在吃力,可以对照着 Kotlin 中文站的翻译来阅读。

在看完官方文档的很长一段时间,我险些只知道 GlobalScope。的确,官方文档上基本从头至尾都是在用 GlobalScope 写示例代码。所以一部分开辟者,也包罗我自己,在写自己的代码时也就直接 GlobalScope 了。一次偶尔的机会才发现其实这样的问题是很大的。在 Android 中,一样平常是不发起直接使用 GlobalScope 的。那么,在 Android 中应该怎样精确使用协程呢?再细分一点,怎样直接在 Activity 中使用呢?怎样共同 ViewModel 、LiveData 、LifeCycle 等使用呢?我会通过简朴的示例代码来阐述 Android 上的协程使用,你也可以跟着动手敲一敲。

协程在 Android 上的使用

GlobalScope

在一样平常的应用场景下,我们都希望可以异步进行耗时使命,比如网络请求,数据处置惩罚等等。当我们脱离当前页面的时间,也希望可以取消正在进行的异步使命。这两点,也正是使用协程中所需要注意的。既然不发起直接使用 GlobalScope,我们就先试验一下使用它会是什么结果。

  1. <code>private fun launchFromGlobalScope() {
  2. GlobalScope.launch(Dispatchers.Main) {
  3. val deferred = async(Dispatchers.IO) {
  4. // network request
  5. delay(3000)
  6. "Get it"
  7. }
  8. globalScope.text = deferred.await()
  9. Toast.makeText(applicationContext, "GlobalScope", Toast.LENGTH_SHORT).show()
  10. }
  11. }</code>
复制代码

launchFromGlobalScope() 方法中,我直接通过 GlobalScope.launch() 启动一个协程,delay(3000) 模仿网络请求,三秒后,会弹出一个 Toast 提示。使用上是没有任何问题的,可以正常的弹出 Toast 。但是当你执行这个方法之后,立即按返回键返回上一页面,仍旧会弹出 Toast 。假如是实际开辟中通过网络请求更新页面的话,当用户已经不在这个页面了,就根本没有必要再去请求了,只会浪费资源。GlobalScope 显然并不符合这一特性。Kotlin 文档 中其实也详细说明了,如下所示:

Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Another use of the global scope is operators running in Dispatchers.Unconfined, which don’t have any job associated with them.

Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged.

大抵意思是,Global scope 通常用于启动顶级协程,这些协程在整个应用步伐生命周期内运行,不会被过早地被取消。步伐代码通常应该使用自界说的协程作用域。直接使用 GlobalScope 的 async 大概 launch 方法是剧烈不发起的。

GlobalScope 创建的协程没有父协程,GlobalScope 通常也不与任何生命周期组件绑定。除非手动管理,否则很难满足我们实际开辟中的需求。所以,GlobalScope 能不消就只管不消。

MainScope

官方文档中提到要使用自界说的协程作用域,当然,Kotlin 已经给我们提供了合适的协程作用域 MainScope 。看一下 MainScope 的界说:

  1. <code>public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)</code>
复制代码

记着这个界说,在后面 ViewModel 的协程使用中也会借鉴这种写法。

给我们的 Activity 实现自己的协程作用域:

  1. <code>class BasicCorotineActivity : AppCompatActivity(), CoroutineScope by MainScope() {}</code>
复制代码

通过扩展函数 launch() 可以直接在主线程中启动协程,示例代码如下:

  1. <code>private fun launchFromMainScope() {
  2. launch {
  3. val deferred = async(Dispatchers.IO) {
  4. // network request
  5. delay(3000)
  6. "Get it"
  7. }
  8. mainScope.text = deferred.await()
  9. Toast.makeText(applicationContext, "MainScope", Toast.LENGTH_SHORT).show()
  10. }
  11. }</code>
复制代码

末了别忘了在 onDestroy() 中取消协程,通过扩展函数 cancel() 来实现:

  1. <code>override fun onDestroy() {
  2. super.onDestroy()
  3. cancel()
  4. }</code>
复制代码

现在来测试一下 launchFromMainScope() 方法吧!你会发现这完全符合你的需求。实际开辟中可以把 MainScope 整合到 BaseActivity 中,就不需要重复誊写模板代码了。

ViewModelScope

假如你使用了 MVVM 架构,根本就不会在 Activity 上誊写任何逻辑代码,更别说启动协程了。这个时间大部分工作就要交给 ViewModel 了。那么怎样在 ViewModel 中界说协程作用域呢?还记得上面 MainScope() 的界说吗?没错,搬过来直接使用就可以了。

  1. <code>class ViewModelOne : ViewModel() {
  2. private val viewModelJob = SupervisorJob()
  3. private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  4. val mMessage: MutableLiveData<String> = MutableLiveData()
  5. fun getMessage(message: String) {
  6. uiScope.launch {
  7. val deferred = async(Dispatchers.IO) {
  8. delay(2000)
  9. "post $message"
  10. }
  11. mMessage.value = deferred.await()
  12. }
  13. }
  14. override fun onCleared() {
  15. super.onCleared()
  16. viewModelJob.cancel()
  17. }
  18. }</code>
复制代码

这里的 uiScope 其实就等同于 MainScope。调用 getMessage() 方法和之前的 launchFromMainScope() 结果也是一样的,记得在 ViewModel 的 onCleared() 回调里取消协程。

你可以界说一个 BaseViewModel 来处置惩罚这些逻辑,克制重复誊写模板代码。然而 Kotlin 就是要让你做同样的事,写更少的代码,于是 viewmodel-ktx 来了。看到 ktx ,你就应该明白它是来简化你的代码的。引入如下依赖:

  1. <code>implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha03"</code>
复制代码

然后,什么都不需要做,直接使用协程作用域 viewModelScope 就可以了。viewModelScope 是 ViewModel 的一个扩展属性,界说如下:

  1. <code>val ViewModel.viewModelScope: CoroutineScope
  2. get() {
  3. val scope: CoroutineScope? = this.getTag(JOB_KEY)
  4. if (scope != null) {
  5. return scope
  6. }
  7. return setTagIfAbsent(JOB_KEY,
  8. CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
  9. }</code>
复制代码

看下代码你就应该明白了,照旧认识的那一套。当 ViewModel.onCleared() 被调用的时间,viewModelScope 会主动取消作用域内的全部协程。使用示例如下:

  1. <code>fun getMessageByViewModel() {
  2. viewModelScope.launch {
  3. val deferred = async(Dispatchers.IO) { getMessage("ViewModel Ktx") }
  4. mMessage.value = deferred.await()
  5. }
  6. }</code>
复制代码

写到这里,viewModelScope 是能满足需求的最简写法了。实际上,写完全篇,viewModelScope 仍旧是我以为的最好的选择。

LiveData

Kotlin 同样为 LiveData 赋予了直接使用协程的本领。添加如下依赖:

  1. <code>implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03"</code>
复制代码

直接在 liveData {} 代码块中调用需要异步执行的挂起函数,并调用 emit() 函数发送处置惩罚结果。示例代码如下所示:

  1. <code>val mResult: LiveData<String> = liveData {
  2. val string = getMessage("LiveData Ktx")
  3. emit(string)
  4. }</code>
复制代码

你大概会好奇这里似乎并没有任何的表现调用,那么,liveData 代码块是在什么执行的呢?当 LiveData 进入 active 状态时,liveData{ } 会主动执行。当 LiveData 进入 inactive 状态时,颠末一个可配置的 timeout 之后会主动取消。假如它在完成之前就取消了,当 LiveData 再次 active 的时间会重新运行。假如上一次运行成功结束了,就不会再重新运行。也就是说只有主动取消的 liveData{ } 可以重新运行。其他缘故原由(比如 CancelationException)导致的取消也不会重新运行。

所以 livedata-ktx 的使用是有肯定限定的。对于需要用户主动革新的场景,就无法满足了。在一次完备的生命周期内,一旦成功执行完成一次,就没有办法再触发了。 这句话不知道对不对,我个人是这么理解的。因此,照旧 viewmodel-ktx 的适用性更广,可控性也更好。

LifecycleScope

  1. <code>implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha03"</code>
复制代码

lifecycle-runtime-ktx 给每个 LifeCycle 对象通过扩展属性界说了协程作用域 lifecycleScope 。你可以通过 lifecycle.coroutineScope 大概 lifecycleOwner.lifecycleScope 进行访问。示例代码如下:

  1. <code>fun getMessageByLifeCycle(lifecycleOwner: LifecycleOwner) {
  2. lifecycleOwner.lifecycleScope.launch {
  3. val deferred = async(Dispatchers.IO) { getMessage("LifeCycle Ktx") }
  4. mMessage.value = deferred.await()
  5. }
  6. }</code>
复制代码

当 LifeCycle 回调 onDestroy() 时,协程作用域 lifecycleScope 会主动取消。在 Activity/Fragment 等生命周期组件中我们可以很方便的使用,但是在 MVVM 中又不会过多的在 View 层进行逻辑处置惩罚,viewModelScope 基本就可以满足 ViewModel 中的需求了,lifecycleScope 也显得有点那么食之无味。但是他有一个特别的用法:

  1. <code>suspend fun <T> Lifecycle.whenCreated()
  2. suspend fun <T> Lifecycle.whenStarted()
  3. suspend fun <T> Lifecycle.whenResumed()
  4. suspend fun <T> LifecycleOwner.whenCreated()
  5. suspend fun <T> LifecycleOwner.whenStarted()
  6. suspend fun <T> LifecycleOwner.whenResumed()</code>
复制代码

可以指定至少在特定的生命周期之后再执行挂起函数,可以进一步减轻 View 层的负担。

总结

以上简朴的介绍了在 Android 中公道使用协程的一些方案,示例代码已上传至 Github。关于 MVVM + 协程 的实战项目,可以看看我的开源项目 wanandroid,同时也期待你宝贵的意见。

文章首发微信公众号: 秉心说 , 专注 Java 、 Android 原创知识分享,LeetCode 题解。

更多最新原创文章,扫码关注我吧!







来源:https://www.cnblogs.com/bingxinshuo/p/11717209.html
C#论坛 www.ibcibc.com IBC编程社区
C#
C#论坛
IBC编程社区
*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则