简要
首先简要介绍一下kotlin协程作用域的三种类型。
类型 | 产生方式 | 异常传播特征 |
---|---|---|
顶级作用域 | GlobalScope创建 | 异常不向外传播。异常到达顶级作用域后,如果还没有被处理,会抛给当前的exceptionHandler,如果没有则给当前线程的uncaughtExceptionHandler |
协同作用域 | Job嵌套、coroutineScope创建 | 异常双传播。异常会向上向下双向传播。 |
主从作用域 | 可通过supervisorScope创建,另外MainScope和lifecycleScope内部设置了 | 异自上而下单项传播。父协程不去受理子协程产生的异常。但是一旦父布局出现了异常,则会直接取消子协程。 |
相关引用,kotlin协程库这里使用的版本是:1.4.2,可点击查看了解目前自己当前kotlin版本对应的协程库版本。
project.ext.kotlin_coroutines_version = "1.4.2"
//kotlin协程标准库 GlobalScope
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
//kotlin协程Android支持 MainScope()
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
//lifecycle
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
GlobalScope
GlobalScope继承自CoroutineScope。
kotlin协程标准库里面是没有MainScope以及lifecycleScope这些花里胡哨的东西的😯,一般使用GlobalScope.launch来启动协程即可。
val job = GlobalScope.launch {
// TODO: 2021/8/29 do
}
使用launch启动可以设置启动模式,调度器以及异常处理器Handler,如下所示:
val coroutineExceptionHandler = CoroutineExceptionHandler {
coroutineContext, throwable ->
throwable.printStackTrace()
}
val job = GlobalScope.launch(
Dispatchers.Main + coroutineExceptionHandler,
CoroutineStart.DEFAULT
) {
// TODO: 2021/8/29 do
}
调度器
调度器 | 线程,使用场景 |
---|---|
Default | 线程池,适合cpu密集的操作,比如计算 |
Main | UI线程 比如计算 |
Io | 线程池,适合io操作,比如网络请求等 |
Unconfined | 不调度,直接执行。最后执行的线程取决于挂起函数恢复的时候的调度器 |
异常拦截器
设置方式,如上图所示,如果运行过程中挂起函数或者协程体体内部抛出异常(没有被处理),则会最终调用到异常拦截器。
协同作用域首先会看父协程是否处理异常,如果不处理才检查自己是否存在异常处理器等操作。同时会将异常下自己的子协程传递,取消子协程的进行。
顶级作用域会直接进行检查自己是否存在异常处理器等操作。
主从作用域其实就是协同作用域,但是它默认不处理子协程的异常,所以子协程只能处理,则就显示出,异常向下传播的镜像。不会影响自己的其他执行模块。比较适用于UI驱动的程序。比如说Android。
启动模式
启动模式 | 功能特性 |
---|---|
DEFAULT | 立即开始调度协程体,调度前若取消则直接取消 |
ATOMIC | 立即开始调度协程体,直到第一个挂起点之前不能取消 |
LAZY | 只有在需要(start/join/await)时才开始调度。其实创建协程有两种方式,一种是createCoroutine().resume(Unit);另一种是startCoroutine()内部调用了resume。而对于lazy的模式来说是先调用了createCoroutine,然后在需要的时候调用了resume启动协程 |
UNDISPATCHED | 立即在当前协程执行协程体,知道遇到第一个挂起点(后续的执行线程取决于挂起点恢复执行时候的调度器) |
好了,现在说一说GlobalScope的问题🙄。
- 如果是Android基本都要调度到主线程进行操作,但是GlobalScope.launch默认的调度器是Default。每次都要显示的写Main不是很方便。
- 上面也说到了Android这种UI驱动的程序,比较适合主从作用域,但是GlobalScope是顶级作用域。那有人就说了supervisorScope启动的不是主从作用域吗?但是supervisorScope是一个挂起函数(可自行查看源码。其实是内部引用了一个私有的SupervisorCoroutine类,继承自ScopeCoroutine。重写了childCancelled方法,就干了一件事,返回了false。简单来说就是不处理子协程的异常🤦♀️,这可忒不负责任了😢)。如果要调用supervisorScope必须要在一个协程或者挂起函数内。啊 ~ 这。那我不得先启动一个协程?😅
- 还要一个问题,内存泄漏的问题。需要在页面销毁的适合取消掉当前协程。对于GlobalScope来说,就必须进行手动调用cancel的操作了。emmm,这波操作不仅麻烦而且危险(万一忘了取消咋整)!
MainScope()
先看一下源码:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
老话说得好啊,柿子的挑软的捏!Dispatchers.Main