首先简要介绍一下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继承自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的问题。
先看一下源码:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
老话说得好啊,柿子的挑软的捏!Dispatchers.Main 好嘛!默认调度器是主线程,解决了上面说的第一个问题。
那SupervisorJob()是个啥呢?再进一步看看
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
emm,结局显而易见。顺着方法一路点进去,发现了一个重写的childCancelled返回了false。还记得上面我写的supervisorScope简要分析吗?supervisorScope不就是也只干了这么个事情嘛。所以说这玩意其实也是一个主从作用域。解决了第二个问题。
顺手提一嘴,会不会有什么好奇为什么SupervisorJob() + Dispatchers.Main以及上面刚开始写的Dispatchers.Main + coroutineExceptionHandler,是个什么鬼,为什么可以加呢。其实调度器和异常处理器它就是CoroutineContext;而作用域接口内部就一个常量就是CoroutineContext。CoroutineContext是什么呢?这玩意就是协程上下文。说白了就是一个集合。在协程内部操作的过程中调度器、异常处理都是从CoroutineContext里面取的,包括作用域分发异常,也是从上下文中取得得父子协程。为什么可以加呢?因为内部重写了operator fun plus操作符,所以能进行加的操作(理解为集合即可,想要具体了解可以考虑撸一波源码)。
那好像就剩下生命周期没有解决了。再去瞅瞅Android对lifecycleScope做了什么封装吧!
emm,看源码
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
返回了LifecycleCoroutineScope,而LifecycleCoroutineScope可以启动协程,所以肯定是CoroutineScope作用域的子类。另外receiver是LifecycleOwner,所以先猜测:应该是绑定了Android的声明周期了。
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
本着带着问题看源码的思维,直接忽略掉那些CAS的操作。简单分析一波。返回了LifecycleCoroutineScopeImpl对象,而LifecycleCoroutineScopeImpl是LifecycleCoroutineScope类型的。所以重点就是LifecycleCoroutineScopeImpl了!
仔细看LifecycleCoroutineScopeImpl传入了this以及SupervisorJob() + Dispatchers.Main.immediate。this不就是生命周期Lifecycle嘛。而SupervisorJob()上面已经分析过了,成为主从作用域。Dispatchers.Main.immediate就是切换到主线程。那可能有人就会问题了,immediate是个什么鬼,别着急。先分析完主流程。其他的写在后面感兴趣的话可以看看。
下面看看LifecycleCoroutineScopeImpl的实现源码!
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel()
}
}
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
}
方便上面的分析,放上生命周期状态枚举类。DESTROYED的ordinal就是0,RESUMED的ordinal就是4。枚举可以直接使用ordinal比较大小。
public enum State {
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
构造LifecycleCoroutineScopeImpl的时候判断一波,如果当前是DESTROYED直接取消协程。
剩下就两个函数,register()和onStateChanged(source: LifecycleOwner, event: Lifecycle.Event)。
**register()**在创建LifecycleCoroutineScopeImpl的时候调用了一下(可以看上面的源码)。简单来说将当前注册进了生命周期观察者当中。
onStateChanged():每次生命周期变化的时候被调用(不了解的可以自行了解一波lifecycle)。每次都判断如果当前生命周期 在DESTROYED之后就取消掉协程,同时移除观察者! 破案了!!!
所以 lifecycleScope 完美解决了 上面说的GlobalScope的问题。不仅方便还安全!!!
lifecycleScope虽然比较方便且不用担心内存泄漏的问题!但是是有使用限制的。网上大部分都说lifecycleScope 只能在Activity、Fragment中使用其实是不太准确的。上面分析了一下lifecycleScope是LifecycleOwner的扩展函数,receiver是LifecycleOwner。因为Activity、Fragment和默认绑定了LifecycleOwner所以可以直接使用。但是理论上来说 只要是能获取到LifecycleOwner的地方都是可以使用lifecycleScope的!
LifecycleCoroutineScope
public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
internal abstract val lifecycle: Lifecycle
public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenCreated(block)
}
public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenStarted(block)
}
public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenResumed(block)
}
}
比如说LifecycleCoroutineScope内部就提供了几个方法,当你不是在Activity、Fragment内部调用的时候,可以调用使用这几个方法,准确的在对应的声明周期内部执行。内部其实是一个DispatchQueue封装了ArrayDeque队列。判断生命周期如果小于当前可执行的生命周期则加入队列,等到对应生命周期来到在取出执行。
但是有一个小问题。不知道大家发现没有,使用这几个方法的时候没有办法设置异常处理器!直接启用了一个默认的launch还没有给传入上下文的入口。
所以如果你想使用这几个方法还想传入异常处理器的话,可以这么写:自己写个launch。
val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
throwable.printStackTrace()
}
lifecycleScope.launch(coroutineExceptionHandler) {
lifecycle.whenCreated {
// TODO: 2021/8/29 do
}
}
immediate
在分析一波immediate吧,这玩意是啥,按照本来的理解Main已经是主线程了,immediate是个什么操作。
先简单介绍一下CoroutineDispatcher 吧,调度器较为上层的基类吧。immediate必然也是调度器,所以肯定实现了CoroutineDispatcher 。而CoroutineDispatcher 里面有两个方法。isDispatchNeeded和dispatch。
isDispatchNeeded:如果协程的执行应该使用 [dispatch] 方法执行,则返回 true
。大多数调度程序的默认行为是返回 true
。
dispatch:将可运行的 [block] 的执行分派到给定 [context] 中的另一个线程。
如果isDispatchNeeded返回true则执行dispatch进行调度。
其实就是isDispatchNeeded返回true则执行dispatch进行调度。
调度器是协程的拦截器(在挂起函数恢复时调用),然后使用continuation包装调用然后达到在另一个线程调度的效果。上面说的在DispatchedContinuation类的resumeWith的方法有体现,源码如下。先判断了isDispatchNeeded然后进行dispatch调度。
override fun resumeWith(result: Result<T>) {
val context = continuation.context
val state = result.toState()
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_ATOMIC
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_ATOMIC) {
withCoroutineContext(this.context, countOrElement) {
continuation.resumeWith(result)
}
}
}
}
上面知识知道后,在看一下immediate的实现。
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
//省略。。
override val immediate: HandlerContext = _immediate ?:
HandlerContext(handler, name, true).also { _immediate = it }
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || Looper.myLooper() != handler.looper
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
//省略。。
}
最终会发现immediate的最终实现在HandlerContext 。具体关注isDispatchNeeded只有 !invokeImmediately || Looper.myLooper() != handler.looper 返回true的时候才会进行调度。
看一下 invokeImmediately。是全局变量通过构造函数传进去的,而在immediate属性声明的时候就进行了赋值默认传了true。所以,上面的判断就完全取决于Looper.myLooper() != handler.looper。handler.looper其实是主线程的looper,所以在lifecycleScope里面,简单来说就是如果当前线程不是主线程话进行调度,否则不进行调度,省去了一波资源开销。
那么为什么我说handler.looper是主线程looper呢?因为:调用Main的时候传入了主线程的looper
internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true)) }.getOrNull()
而immediate的注释是这么写的:
Returns dispatcher that executes coroutines immediately when it is already in the right context (e.g. current looper is the same as this handler’s looper) without an additional [re-dispatch][CoroutineDispatcher.dispatch].
翻译一下就是:
返回当它已经在正确的上下文中时立即执行协程的调度程序(例如当前循环程序与此处理程序的循环程序相同),而无需额外的 [re-dispatch][CoroutineDispatcher.dispatch]。
所以,immediate就是优化了一波线程转换调用。节省了一波资源!