Kotlin Coroutine
原文链接
今天我们来聊聊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相关推荐
- 深入理解 Kotlin coroutine (二)
原文链接:https://github.com/enbandari/Kotlin-Tutorials 上周我们把 Kotlin Coroutine 的基本 API 挨个讲了一下,也给出了一些简单的封装 ...
- 深入理解 Kotlin Coroutine (一)
原文链接:https://github.com/enbandari/Kotlin-Tutorials 本文主要介绍 Kotlin Coroutine 的基础 API,有关 Kotlinx.Corout ...
- Kotlin Coroutine(二):作用域及取消
一.协程作用域 定义协程必须指定其 CoroutineScope .CoroutineScope 可以对协程进行追踪,即使协程被挂起也是如此.同调度程序 (Dispatcher) 不同,Corouti ...
- kotlin coroutine源码解析之Job启动流程
目录 Job启动流程 launch流程分析 父子Job关联分析 结论 Job启动流程 job启动流程,我们先从一段最简单使用协程的代码开始,进行代码跟跟踪,顺便引出几个关键的概念,在后面章节里面去单独 ...
- 【Kotlin协程】基于RxJava项目的Coroutine改造
最近,Android宣布彻底废弃AsyncTask,推荐Coroutine作为首选的异步编程方案. 如果说AsyncTask被Coroutine替代毫无悬念,那RxJava与Coroutine如何取舍 ...
- pdf 深入理解kotlin协程_Kotlin协程实现原理:挂起与恢复
今天我们来聊聊Kotlin的协程Coroutine. 如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine? 如果你已经接触过协程,但对协程的原理存 ...
- Kotlin学习笔记21 协程part1 基本概念
参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 本节先介绍协程的相关概念 概念可能枯燥,我们先要了解协程中的相关概念 然后结合代码理解这些概念 加深印象 协程的定义 协程 ...
- 一文看透 Kotlin 协程本质
前言 公司开启新项目了,想着准备亮一手 Kotlin 协程应用到项目中去,之前有对 Kotlin 协程的知识进行一定量的学习,以为自己理解协程了,结果--实在拿不出手! 为了更好的加深记忆和理解,更全 ...
- Kotlin零基础入门到精通(精选)
Kotlin零基础入门到精通(精选) 一. Kotlin课程概述 1.1 课程安排: 1.2 什么是Kotlin? 1.3 Kotlin的发展历程 1.4 学习目标 1.5 必备知识 1.6 参考资料 ...
最新文章
- MVC案例-架构分析
- 买卖股票 状态机模型的理解
- T-SQL基础(三)之子查询与表表达式
- 【Python】如何在文件夹里批量分割图片?
- c++中STL的常用算法--1(函数对象,谓词,内建函数对象)
- markdown 常用语法总结 - 个人版
- 53-C++ CH08 01
- html文件可以分层吗,css分层是用什么标记?
- 百度地图API实现地理围栏
- 龙蜥社区8问,你关心的问题都在这里
- 刘汝佳第二章习题(前四)
- css样式,鼠标移动上去变成禁用、小手等样式。
- 澤火革 (易經大意 韓長庚)
- Spring Boot学习案例开源项目
- 新生宝宝为何天生过敏体质 婴儿过敏体质的症状
- Jetpack-Compose-自定义绘制
- ai 如何导出html格式,Adobe Illustrator导出SVG的设置方法
- 2020东京奥运会金牌榜爬虫
- 【echarts 中国地图增加南海九段线】
- SpringBoot的AOP中PointCut表达式详解以及使用