原文链接

今天我们来聊聊Kotlin Coroutine,如果你还没有了解过,那么我要提前恭喜你,因为你将掌握一个新技能,对你的代码方面的提升将是很好的助力。

What Coroutine ?
简单的来说,Coroutine是一个并发的设计模式,你能通过它使用更简洁的代码来解决异步问题。

例如,在Android方面它主要能够帮助你解决以下两个问题:

在主线程中执行耗时任务导致的主线程阻塞,从而使App发生ANR。
提供主线程安全,同时对来自于主线程的网络回调、磁盘操提供保障。
这些问题,在接下来的文章中我都会给出解决的示例。

Callback
说到异步问题,我们先来看下我们常规的异步处理方式。首先第一种是最基本的callback方式。

callback的好处是使用起来简单,但你在使用的过程中可能会遇到如下情形

GatheringVoiceSettingRepository.getInstance().getGeneralSettings(RequestLanguage::class.java).observe(this, { language ->convertResult(language, { enable -> // todo something})})

这种在其中一个callback中回调另一个callback回调,甚至更多的callback都是可能存在。这些情况导致的问题是代码间的嵌套层级太深,导致逻辑嵌套复杂,后续的维护成本也要提高,这不是我们所要看到的。

那么有什么方法能够解决呢?当然有,其中的一种解决方法就是我接下来要说的第二种方式。

Rx系列
对多嵌套回调,Rx系列在这方面处理的已经非常好了,例如RxJava。下面我们来看一下RxJava的解决案例

disposable = createCall().map {// return RequestType
}.subscribeWith(object : SMDefaultDisposableObserver<RequestType>{override fun onNext(t: RequestType) {// todo something}
})

RxJava丰富的操作符,再结合Observable与Subscribe能够很好的解决异步嵌套回调问题。但是它的使用成本就相对提高了,你要对它的操作符要非常了解,避免在使用过程中滥用或者过度使用,这样自然复杂度就提升了。

那么我们渴望的解决方案是能够更加简单、全面与健壮,而我们今天的主题Coroutine就能够达到这种效果。

Coroutine在Kotlin中的基本要点
在Android里,我们都知道网络请求应该放到子线程中,相应的回调处理一般都是在主线程,即ui线程。正常的写法就不多说了,那么使用Coroutine又该是怎么样的呢?请看下面代码示例:

private suspend fun get(url: String) = withContext(Dispatchers.IO) {// to do network requesturl
}private suspend fun fetch() { // 在Main中调用val result = get("https://rousetime.com") // 在IO中调用showToast(result) // 在Main中调用
}

如果fetch方法在主线程调用,那么你会发现使用Coroutine来处理异步回调就像是在处理同步回调一样,简洁明了、行云流水,同时再也没有嵌套的逻辑了。

注意看方法,Coroutine为了能够实现这种简单的操作,增加了两个操作来解决耗时任务,分别为suspend与resume

suspend: 挂起当前执行的协同程序,并且保存此刻的所有本地变量
resume: 从它被挂起的位置继续执行,并且挂起时保存的数据也被还原
解释的有点生硬,简单的来说就是suspend可以将该任务挂起,使它暂时不在调用的线程中,以至于当前线程可以继续执行别的任务,一旦被挂起的任务已经执行完毕,那么就会通过resume将其重新插入到当前线程中。

所以上面的示例展示的是,当get还在请求的时候,fetch方法将会被挂起,直到get结束,此时才会插入到主线程中并返回结果。

另外需要注意的是,suspend方法只能够被其它的suspend方法调用或者被一个coroutine调用,例如launch。

Dispatchers
另一方面Coroutine使用Dispatchers来负责调度协调程序执行的线程,这一点与RxJava的schedules有点类似,但不同的是Coroutine一定要执行在Dispatchers调度中,因为Dispatchers将负责resume被suspend的任务。

Dispatchers提供三种模式切换,分别为

Dispatchers.Main: 使Coroutine运行中主线程,以便UI操作
Dispatchers.IO: 使Coroutine运行在IO线程,以便执行网络或者I/O操作
Dispatchers.Default: 在主线程之外提高对CPU的利用率,例如对list的排序或者JSON的解析。
再来看上面的示例

private suspend fun get(url: String) = withContext(Dispatchers.IO) {// to do network requesturl
}private suspend fun fetch() { // 在Main中调用val result = get("https://rousetime.com") // 在IO中调用showToast(result) // 在Main中调用
}

为了让get操作运行在IO线程,我们使用withContext方法,对该方法传入Dispatchers.IO,使得它闭包下的任务都处于IO线程中,同时witchContext也是一个suspend函数。

创建Coroutine
上面提到suspend函数只能在相应的suspend中或者Coroutine中调用。那么Coroutine又该如何创建呢?

有两种方式,分别为launch与async

launch: 开启一个新的Coroutine,但不返回结果
async: 开启一个新的Coroutine,但返回结果
还是上面的例子,如果我们需要执行fetch方法,可以使用launch创建一个Coroutine

private fun excute() {CoroutineScope(Dispatchers.Main).launch {fetch()}
}

另一种async,因为它返回结果,如果要等所有async执行完毕,可以使用await或者awaitAll

    private suspend fun fetchAll() {coroutineScope {val deferredFirst = async { get("first") }val deferredSecond = async { get("second") }deferredFirst.await()deferredSecond.await()//            val deferred = listOf(
//                    async { get("first") },
//                    async { get("second") }
//            )
//            deferred.awaitAll()}}

所以通过await或者awaitAll可以保证所有async完成之后再进行resume调用。

Architecture Components
如果你使用了Architecture Component,那么你也可以在其基础上使用Coroutine,因为Kotlin Coroutine已经提供了相应的api并且定制了CoroutineScope。

如果你还不了解Architecture Component,强烈推荐你阅读我的Android Architecture Components 系列
在使用之前,需要更新architecture component的依赖版本,如下所示

object Versions {const val arch_version = "2.2.0-alpha01"const val arch_room_version = "2.1.0-rc01"
}object Dependencies {val arch_lifecycle = "androidx.lifecycle:lifecycle-extensions:${Versions.arch_version}"val arch_viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.arch_version}"val arch_livedata = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.arch_version}"val arch_runtime = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.arch_version}"val arch_room_runtime = "androidx.room:room-runtime:${Versions.arch_room_version}"val arch_room_compiler = "androidx.room:room-compiler:${Versions.arch_room_version}"val arch_room = "androidx.room:room-ktx:${Versions.arch_room_version}"
}

ViewModelScope
在ViewModel中,为了能够使用Coroutine提供了viewModelScope.launch,同时一旦ViewModel被清除,对应的Coroutine也会自动取消。

fun getAll() {viewModelScope.launch {val articleList = withContext(Dispatchers.IO) {articleDao.getAll()}adapter.clear()adapter.addAllData(articleList)}
}

在IO线程通过articleDao从数据库取数据,一旦数据返回,在主线程进行处理。如果在取数据的过程中ViewModel已经清除了,那么数据获取也会停止,防止资源的浪费。

LifecycleScope
对于Lifecycle,提供了LifecycleScope,我们可以直接通过launch来创建Coroutine

private fun coroutine() {lifecycleScope.launch {delay(2000)showToast("coroutine first")delay(2000)showToast("coroutine second")}
}

因为Lifecycle是可以感知组件的生命周期的,所以一旦组件onDestroy了,相应的LifecycleScope.launch闭包中的调用也将取消停止。

lifecycleScope本质是Lifecycle.coroutineScope

val LifecycleOwner.lifecycleScope: LifecycleCoroutineScopeget() = lifecycle.coroutineScopeoverride fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {lifecycle.removeObserver(this)coroutineContext.cancel()}}

它会在onStateChanged中监听DESTROYED状态,同时调用cancel取消Coroutine。

另一方面,lifecycleScope还可以根据Lifecycle不同的生命状态进行suspend处理。例如对它的STARTED进行特殊处理

private fun coroutine() {lifecycleScope.launchWhenStarted {}lifecycleScope.launch {whenStarted {  }delay(2000)showToast("coroutine first")delay(2000)showToast("coroutine second")}
}

不管是直接调用launchWhenStarted还是在launch中调用whenStarted都能达到同样的效果。

LiveData
LiveData中可以直接使用liveData,在它的参数中会调用一个suspend函数,同时会返回LiveData对象

fun <T> liveData(context: CoroutineContext = EmptyCoroutineContext,timeoutInMs: Long = DEFAULT_TIMEOUT,@BuilderInference block: suspend LiveDataScope<T>.() -> Unit
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)

所以我们可以直接使用liveData来是实现Coroutine效果,我们来看下面一段代码

// Room
@Query("SELECT * FROM article_model WHERE title = :title LIMIT 1")
fun findByTitle(title: String): ArticleModel?
// ViewModel
fun findByTitle(title: String) = liveData(Dispatchers.IO) {MyApp.db.articleDao().findByTitle(title)?.let {emit(it)}
}
// Activity
private fun checkArticle() {vm.findByTitle("Android Architecture Components Part1:Room").observe(this, Observer {})
}

通过title从数据库中取数据,数据的获取发生在IO线程,一旦数据返回,再通过emit方法将返回的数据发送出去。所以在View层,我们可以直接使用checkArticle中的方法来监听数据的状态。

另一方面LiveData有它的active与inactive状态,对于Coroutine也会进行相应的激活与取消。对于激活,如果它已经完成了或者非正常的取消,例如抛出CancelationException异常,此时将不会自动激活。

对于发送数据,还可以使用emitSource,它与emit共同点是在发送新的数据之前都会将原数据清除,而不同点是,emitSource会返回一个DisposableHandle对象,以便可以调用它的dispose方法进行取消发送。

最后我使用Architecture Component与Coroutine写了个简单的Demo,大家可以在Github中进行查看

源码地址: https://github.com/idisfkj/android-api-analysis

Kotlin Coroutine相关推荐

  1. 深入理解 Kotlin coroutine (二)

    原文链接:https://github.com/enbandari/Kotlin-Tutorials 上周我们把 Kotlin Coroutine 的基本 API 挨个讲了一下,也给出了一些简单的封装 ...

  2. 深入理解 Kotlin Coroutine (一)

    原文链接:https://github.com/enbandari/Kotlin-Tutorials 本文主要介绍 Kotlin Coroutine 的基础 API,有关 Kotlinx.Corout ...

  3. Kotlin Coroutine(二):作用域及取消

    一.协程作用域 定义协程必须指定其 CoroutineScope .CoroutineScope 可以对协程进行追踪,即使协程被挂起也是如此.同调度程序 (Dispatcher) 不同,Corouti ...

  4. kotlin coroutine源码解析之Job启动流程

    目录 Job启动流程 launch流程分析 父子Job关联分析 结论 Job启动流程 job启动流程,我们先从一段最简单使用协程的代码开始,进行代码跟跟踪,顺便引出几个关键的概念,在后面章节里面去单独 ...

  5. 【Kotlin协程】基于RxJava项目的Coroutine改造

    最近,Android宣布彻底废弃AsyncTask,推荐Coroutine作为首选的异步编程方案. 如果说AsyncTask被Coroutine替代毫无悬念,那RxJava与Coroutine如何取舍 ...

  6. pdf 深入理解kotlin协程_Kotlin协程实现原理:挂起与恢复

    今天我们来聊聊Kotlin的协程Coroutine. 如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine? 如果你已经接触过协程,但对协程的原理存 ...

  7. Kotlin学习笔记21 协程part1 基本概念

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 本节先介绍协程的相关概念 概念可能枯燥,我们先要了解协程中的相关概念 然后结合代码理解这些概念 加深印象 协程的定义 协程 ...

  8. 一文看透 Kotlin 协程本质

    前言 公司开启新项目了,想着准备亮一手 Kotlin 协程应用到项目中去,之前有对 Kotlin 协程的知识进行一定量的学习,以为自己理解协程了,结果--实在拿不出手! 为了更好的加深记忆和理解,更全 ...

  9. Kotlin零基础入门到精通(精选)

    Kotlin零基础入门到精通(精选) 一. Kotlin课程概述 1.1 课程安排: 1.2 什么是Kotlin? 1.3 Kotlin的发展历程 1.4 学习目标 1.5 必备知识 1.6 参考资料 ...

最新文章

  1. MVC案例-架构分析
  2. 买卖股票 状态机模型的理解
  3. T-SQL基础(三)之子查询与表表达式
  4. 【Python】如何在文件夹里批量分割图片?
  5. c++中STL的常用算法--1(函数对象,谓词,内建函数对象)
  6. markdown 常用语法总结 - 个人版
  7. 53-C++ CH08 01
  8. html文件可以分层吗,css分层是用什么标记?
  9. 百度地图API实现地理围栏
  10. 龙蜥社区8问,你关心的问题都在这里
  11. 刘汝佳第二章习题(前四)
  12. css样式,鼠标移动上去变成禁用、小手等样式。
  13. 澤火革 (易經大意 韓長庚)
  14. Spring Boot学习案例开源项目
  15. 新生宝宝为何天生过敏体质 婴儿过敏体质的症状
  16. Jetpack-Compose-自定义绘制
  17. ai 如何导出html格式,Adobe Illustrator导出SVG的设置方法
  18. 2020东京奥运会金牌榜爬虫
  19. 【echarts 中国地图增加南海九段线】
  20. SpringBoot的AOP中PointCut表达式详解以及使用

热门文章

  1. 重视网络安全,从部署SSL证书开始
  2. 深入MySQL复制(一)
  3. 【项目总结】玛嘉环境物联网平台(大三学生独立完成的真实企业外包项目)/网脉通用物联网平台/网脉铁塔监测系统
  4. .NET简谈设计模式之(策略模式)
  5. word发送错误报告。
  6. CSS——餐厅选择器答案
  7. 打包Asp.Net 网站成为一个exe 方便快捷的进行客户演示
  8. 钢之炼金术师之等价交换铃声 钢之炼金术师之等价交换手机铃声...
  9. 百度定位SDK无法定位
  10. echarts画布_Echarts绘图用法