android kotlin 协程(二)
config:
-
system: macOS
-
android studio: 2022.1.1 Electric Eel
-
gradle: gradle-7.5-bin.zip
-
android build gradle: 7.1.0
-
Kotlin coroutine core: 1.6.4
tips:前面几篇全都是协程的根本运用,没有源码,等后面对协程有个根本理解之后,才会简单的剖析一下源码!
上一篇(android kotlin coroutine 根本入门)
看完本篇你能学会什么:
-
CoroutineDispatcher // 协程调度器 用来切换线程
-
CoroutineName // 协程姓名
-
CoroutineStart // 协程发动形式
-
CoroutineException // launch / async 捕获反常
-
GlobalCoroutineException // 大局捕获反常
CoroutineDispatcher 协程调度器
界说: 依据姓名也能够看出来, 协程调度器, 首要用来切换线程,首要有4种
- Dispatchers.Main – 运用此调度程序可在 Android 主线程上运转协程。
- Dispatchers.IO – 此调度程序经过了专门优化,适合在主线程之外履行磁盘或网络 I/O。示例包含运用 Room 组件、从文件中读取数据或向文件中写入数据,以及运转任何网络操作。
- Dispatchers.Default – 此调度程序经过了专门优化,适合在主线程之外履行占用大量 CPU 资源的作业。用例示例包含对列表排序和解析 JSON。
- Dispatchers.Unconfined-始终和父协程运用同一线程
官方文档介绍
先来看一个简单的例子:
这行代码的意思是敞开一个协程,他的作用域在子线程上
能够看出,只需设置DIspatchers.IO 就能够切换线程
tips: 这儿我运用的是协程调试才能够打印出协程编号
1.
-Dkotlinx.coroutines.debug
运用协程DIspatcher切换线程的时分,需求留意的是,子协程假如调度了,就运用调度后的线程,假如没有调度,始终保持和父协程相同的线程
这儿的调度便是指的是否有DIspatcher.XXX
例如这样:
对于coroutine#4,他会跟从 coroutine#3 的线程
coroutine#3 会 跟从 coroutine#2 的线程
coroutine#2 有本身的调度器IO,所以悉数都是IO线程
再来看一段代码:
withContext() 是用来切换线程,这儿切换到主线程,但是输出的成果并没有切换到主线程
withContext{} 与launch{} 调度的差异:
- withContext 在原有协程上切换线程
- launch 创立一个新的协程来切换线程
这儿我感觉是kotlin对JVM支撑还不够
由于本身JVM渠道就没有Main线程,Main线程是对与Android渠道的
所以咱们将这段代码拿到android渠道试一下
能够看出,能够切换,咱们以android渠道为主!
这儿需求留意的是:
JVM渠道上没有Dispatcher.Main, 由于Main仅仅针对android的,所以假如想要在JVM渠道上切换Main线程,
需求添加:
implementation (“org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4”)
而且在dispatcher.Main之前调用 Dispatchers.setMain(Dispatchers.Unconfined)
gitHub issues
现在咱们知道了经过Dispatcher.XXX 就能够切换线程, 那么Dispatcher.XXX是什么呢? 这儿以Dispatcher.IO为例
能够看出,承继关系为:
Dispatcher.IO = DefaultIoScheduler => ExecutorCoroutineDispatcher => CoroutineDispatcher => AbstractCoroutineContextElement => Element => CoroutineContext
终究都是 CoroutineContext 的子类!
完好代码
CoroutineName 协程姓名
**界说:**协程姓名, 子协程会承继父协程的姓名, 假如协程种有自己的姓名,那么就优先运用自己的
这块代码比较简单,就不废话了
能够看出,CoroutineName也是CoroutineContext的子类, 假如说
现在咱们现在想要切换到子线程上咱们该怎么做?
经过方才的代码,咱们知道DIspatcher.XXX 其实质便是CoroutineContext, 那么咱们就能够经过内置的操作符重载来完结两个功用的一起操作
完好代码
CoroutineStart 协程发动形式
界说: coroutineStart 用来操控协程调度器,以及协程的履行时机等
- CoroutineStart.DEFAULT: 当即依据其上下文安排协程履行;
- CoroutineStart.LAZY: 懒加载,不会当即履行,只要调用的时分才会履行
- CoroutineStart.ATOMIC: 常合作Job#cancel()来运用, 假如协程体中有新的挂起点,调用Job#cancel()时 撤销挂起点之后的代码,不然悉数撤销
- CoroutineStart.UnDISPATCHED: 不进行任何调度,包含线程切换等, 线程状况会跟从父协程保持一致
官方参阅
CoroutineStart.DEFAULT 我相信不必过多赘述, 默许便是这个,直接从 CoroutineStart.LAZY开端
CoroutineStart.LAZY
首要来看一段代码:
能够经过这段代码发现, 其他的协程都履行了,只要采用CoroutineStart.LAZY的协程没有履行,而且runBlocking 会一向等待他履行
那么只需求调用Job#start() 或者 job#join() 即可
CoroutineStart.ATOMIC
tips:该属性现在还在试验阶段
先来看正常作用:
在这段代码中,咱们敞开了一个协程,然后当即cancel了,协程中的代码没有履行
假如改成 CoroutineStart.ATOMIC 会发生什么情况呢?
能够惊讶的发现,竟然撤销协程没有作用!
那么这个CoroutineStart.ATOMIC到底有什么用呢?
再来看一段代码:
能够看出, CoroutineStart.ATOMIC 会将挂起点之后的代码给cancel掉,
即便这儿delay好久,也会当即cancel
再换一种挂起点方式
也仍是相同的成果.
Coroutine.UNDISPATCHED
界说: 不进行任何调度,包含线程切换等, 线程状况会跟从父协程保持一致
首要仍是看默许状况
留意:这儿代码会首要履行:1.main start 和 2. main end
这儿有一个调度的概念,比较抽象:
协程始终都是异步履行的,kotlin协程的底层也是线程, kotlin协程说白了便是一个线程框架,
所以创立协程的时分,其实便是创立了一个线程, 运用线程的时分,咱们会经过Thread#start() 告诉JVM咱们有一个任务需求履行,
然后JVM去分配,最终JVM去履行
这儿调度的大致逻辑和线程相似
只不过协程能够轻易的完结2个线程之前切换,切换回来的过程在协程中咱们叫它康复
这儿扯的有点远,先来看本篇的内容 :)
咱们来看看 Coroutine.UNDISPATCHED有什么作用
能够看出,一旦运用了这种发动形式, 就没有了调度的概念,即便是切换线程(withContext)也杯水车薪
跟从父协程线程状况而改变
说实话,这种发动形式我认为比较鸡肋,和不写这个协程如同也没有很大的差异
完好代码
CoroutineException 协程反常捕获
要点: 协程反常捕获有必要放在最顶层的协程作用域上
最简单的咱们经过try catch 来捕获,这种方法就不说了,
首要咱们来看看 coroutineException的承继关系
CoroutineExceptionHandler => AbstractCoroutineContextElement => Element => CoroutineContext
终究承继自 CoroutineContext
到现在为止,咱们知道了 coroutineContext有4个有用的子类
- Job 用来操控协程生命周期
- CoroutineDispatcher 协程调度器,用来切换线程
- CoroutineName 写成姓名
- CoroutineException 协程反常捕获
首要咱们来剖析 CoroutineScope#launch 反常捕获
捕获反常之前先说一个隐秘: Job不只能够用来操控协程生命周期,还能够用不同的Job 来操控协程的反常捕获
Job合作CoroutineHandler 反常捕获
先来看一段简单的代码:
tip: 假如不写Job 默许便是Job()
能够看出,现在的状况是协程1
出现过错之后,就会反馈给CoroutineExcetionHandler
然后协程2
就不会履行了
SupervisorJob()
假如有一个场景,咱们需求某个子协程出现问题就出现问题,不应该影响到其他的子协程履行,那么咱们就能够用 SupervisorJob()
SupervisorJob() 的特点便是:假如某个子协程出现问题不会影响兄弟协程
Job与 SupervisorJob 的差异也很明显
- Job 某个协程出现问题,会直接影响兄弟协程,兄弟协程不会履行
- SupervisorJob 某个协程出现问题,不会影响兄弟协程.
假如现在场景变一下,现在换成了子协程中出现问题,来看看作用
能够看出, 子协程2
并没有履行 这是默许作用,若在子协程中敞开多个子协程,其实主张写法是这样的
coroutineScope{}
为什么要这么写呢? 明明我不写作用就相同,还得写这玩意,不是闲的没事么
我感觉,作用首要便是一致代码,传递CoroutineScope 例如这样
正常在实际开发中假如吧代码全写到一坨,应该会遭到同行轻视 :]
现在场景又调整了, 方才是子协程出现问题当即终止子协程的兄弟协程
现在调整成了: 某个子协程出现问题,不影响子协程的兄弟协程,就想 SupervisorJob() 类型
superiverScope{}
那就请出了咱们的superiverScope{}
作用域
作用很简单
这儿首要要分清楚
SuperiverScope() 和 superiverScope{} 是不相同的
- SuperiverScope() 是用来操控兄弟协程反常的,而且他是一个类
- superiverScope{} 是用来操控子协程的兄弟协程的,他是一个函数
async捕获反常
要点: async运用 CoroutineExceptionHandler 是捕获不到反常的
例如这样:
async 的反常在 Deferred#await()
中, 还记得上一篇中咱们聊过 Deferred#await()
这个方法会获取到async{} 中的回来成果
假如咱们想要捕获async{} 中的反常,咱们只需求try{} catch{} await即可,例如这样写
async 也能够合作 SupervisorJob() 到达子协程出现问题,不影响兄弟协程履行,例如这样:
怎么让 CoroutineExceptionHandler 监听到async的反常,实质是监听不到的,
但是,咱们知道了deferred#await()
会抛出反常,那么咱们能够套一层 launch{} 这样一来就能够到达咱们想要的作用
suspend fun main() {
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
printlnThread("catch 到了 $throwable")
}
val customScope =
CoroutineScope(SupervisorJob() + CoroutineName("自界说协程") + Dispatchers.IO + exceptionHandler)
val deferred1 = customScope.async {
printlnThread("子协程 1 start")
throw KotlinNullPointerException(" ============= 犯错拉 1")
"协程1履行完结"
}
val deferred2 = customScope.async {
printlnThread("子协程 2 start")
"协程2履行完结"
}
val deferred3 = customScope.async {
printlnThread("子协程 3 start")
throw KotlinNullPointerException(" ============= 犯错拉 3")
"协程3履行完结"
}
customScope.launch {
supervisorScope {
launch {
val result = deferred1.await()
println("协程1 result:$result")
}
launch {
val result = deferred2.await()
println("协程2 result:$result")
}
launch {
val result = deferred3.await()
println("协程3 result:$result")
}
}
}.join()
}
成果为:
子协程 3 start: thread:DefaultDispatcher-worker-2 @自界说协程#3
子协程 2 start: thread:DefaultDispatcher-worker-3 @自界说协程#2
子协程 1 start: thread:DefaultDispatcher-worker-1 @自界说协程#1
协程2 result:协程2履行完结
catch 到了 kotlin.KotlinNullPointerException: ============= 犯错拉 3: thread:DefaultDispatcher-worker-2 @自界说协程#7
catch 到了 kotlin.KotlinNullPointerException: ============= 犯错拉 1: thread:DefaultDispatcher-worker-1 @自界说协程#5
协程捕获反常,终究要的一点便是,协程中的反常会一向向上传递,假如想要 运用 CoroutineExceptionHandler,监听到反常,那么就有必要将 CoroutineExceptionHandler 装备到最尖端的coroutineScope中
完好代码
GlobalCoroutineException 大局反常捕获
需求在本地装备一个捕获监听:
resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler
就和APT相似,假如你玩过APT的话,肯定知道这一步是在做什么
完好代码
下一篇预告:
- 协程履行流程 [入门理解挂起与康复]
- delay() 与 Thread#sleep() 差异
原创不易,您的点赞便是对我最大的支撑!