前段时间面试聊到了协程 , 但自己又很久没去阅读协程相关的源码 ,所以答复的并不是很好。

进程:


面试官  项目中怎样处理多线程相关的
xxx: 运用Kolint协程来处理的
面试官:那你说说协程是怎样运用的
xxx : Activity  ViewModel 都有协程域,直接调用launch办法运用就行
面试官 :那在Application 或许 service 中怎么运用呢
xxx:能够运用GlobalScope
面试官 : 那你说说GlobalScope 有什么留意事项?
xxx  emm。。。

想要答复好这个问题,那必定是对协程有必要要知根知底。 正好这几天看了些相关文章,所以想写篇文章总结一下,希望能帮到大家。

协程是什么?

用轻量级线程来答复其实并不精确,由于协程并不是承继自线程,而是运转在线程之上的。每一个协程都完成了Continuation接口 , 这个接口里边有个resumeWith 办法和context (这个context不是android 中activity承继那个context)。Continuation有许多子类,最初级的子类是SuspendLambda。协程域里 launch/async/withContext等 里边的代码经过编译器编译之后都存在SuspendLambda中。说了这么多,所以协程也能够了解成若干个Continuation协作构成的程序。

协程办法有必要运用suspend 标记,suspend标记的办法经过kt编译器CPS转化后,会在办法末尾添加一个参数Continuation,和将办法的回来值修改为Object 。

SuspendLambda

SuspendLambda会完成Function2接口, 由于 suspend CoroutineScope.() -> Unit 经过kt编译器在java对应着的便是Function2<CoroutineScope, Continuation<? super Unit>, Object> ,所以CoroutineScope.launch(

context: CoroutineContext = EmptyCoroutineContext,

start: CoroutineStart = CoroutineStart.DEFAULT,

block: suspend CoroutineScope.() -> Unit

)办法最后的那个传入block,在java便是 MainActivityonCreateonCreate1,如下:

final class MainActivity$onCreate$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {
    int label;
    MainActivity$onCreate$1(Continuation<? super MainActivity$onCreate$1> continuation) {
        super(2, continuation);
    }
    public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
        return new MainActivity$onCreate$1<>(continuation);
    }
    public final Object invoke(CoroutineScope coroutineScope, Continuation<? super Unit> continuation) {
        return ((MainActivity$onCreate$1) create(coroutineScope, continuation)).invokeSuspend(Unit.INSTANCE);
    }
    public final Object invokeSuspend(Object $result) {
        MainActivity$onCreate$1 mainActivity$onCreate$1;
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        switch (this.label) {
            case 0:
                ResultKt.throwOnFailure($result);
                mainActivity$onCreate$1 = this;
                long r2 = LiveLiterals$MainActivityKt.INSTANCE.m135xec86fb79();
                Continuation continuation = mainActivity$onCreate$1;
                mainActivity$onCreate$1.label = 1;
                if (DelayKt.delay(r2, continuation) == coroutine_suspended) {
                    return coroutine_suspended;
                }
                break;
            case 1:
                mainActivity$onCreate$1 = this;
                ResultKt.throwOnFailure($result);
                break;
            case 2:
                ResultKt.throwOnFailure($result);
                break;
            default:
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        }
        System.out.println(LiveLiterals$MainActivityKt.INSTANCE.m139x894324b7() + Thread.currentThread().getName());
        long r22 = LiveLiterals$MainActivityKt.INSTANCE.m136x7689ba5d();
        Continuation continuation2 = mainActivity$onCreate$1;
        mainActivity$onCreate$1.label = 2;
        if (DelayKt.delay(r22, continuation2) == coroutine_suspended) {
            return coroutine_suspended;
        }
        MainActivity$onCreate$1 mainActivity$onCreate$12 = mainActivity$onCreate$1;
        return Unit.INSTANCE;
    }
}

假如需求切换线程则运用DispatchedContinuation , 这个也是Continuation的子类,并且这个类是承继自Runnable的,假如需求切换线程则把SuspendLambda包装在这个类里边。

假如在当时线程履行则直接调用SuspendLambda#resumeWith办法就行了,resumeWith办法完成在BaseContinuationImpl中。好了暂时先粗略了解这两个子类,关于协程是什么就先说到这里了。

协程效果域

GlobalScope是不支撑cancel的,可是GlobalScope.launch会回来一个job,这个job是支撑撤销的。由于GlobalScope 的EmptyCoroutineContext里是没有Job的。所以更引荐运用 CoroutineScope(Dispatchers.Default) ,这个会在context上加上Job 。

MainScope 是在主线程运用的协程效果域,因此在这个域里不能履行耗时操作的,假如要履行耗时操作有必要要发动子协程并且指定调度器。

协程的发动

协程有必要在协程域里边发动,有四种发动办法DEFAULT 、 ATOMIC、UNDISPATCHED、LAZY ,DEFAULT 发动 是支撑撤销的。协程发动之前先要经过协程调度器去调度到对应的线程,之后才履行协程体内的代码。

launch发动协程不是lazy情况,每次都会新建StandaloneCoroutine,StandaloneCoroutine承继AbstractCoroutine,AbstractCoroutine承继自JobSupport和完成Continuation,这个能够了解为尖端协程,这个协程支撑cancel或许其他job支撑的操作。

withContext 发动协程,会挂起当时协程直到获取到回来值,才康复当时协程履行。

async 发动协程不会挂起当时协程 ,会回来一个Deferred,调用Deferred#await办法假如回来值还没预备好会挂起当时协程。

所以总结下: 这么多发动子协程无非就两种办法,一种挂起当时协程发动,另一种是不挂起当时协程发动。

协程调度器

DEFAULT 调度器 ,经过CoroutineScope.launch发动的时分会先构建出协程上下文,调度器为 Dispatchers.Default 即默认调度器 ,Dispatchers.Default 是一个单例 ,里边的线程数量和当时手机的cpu核数相等。假如是双核的话,调度器为默认调度器的情况,协程里边的代码只能在两个线程跑(不信能够经过Thread.sleep去测试),所以恳求网络只用这个调度器必定不行,两个线程不够跑。

IO调度器,里边最少有64个线程,网络恳求、IO操作都能够运用这个调度器,并且这个调度器也会用到默认调度器中的线程(资源利用最大化)。

调度器中的Worker数量即线程数量,每个Worker有它自己的本地行列,这个行列是一个生产者顾客行列,最大的缓冲阈值为128。

协程履行流程

回到最开端那个代码片段,不管经过什么scope.launch发动协程 ,其实都是调用CoroutineScope的扩展办法launch。经过withContext开端协程会调用到suspendCoroutineUninterceptedOrReturn会挂起当时协程。

以哪种办法发动协程终究都会履行代码(以默认发动为例), block.startCoroutineCancellable(completion) ,这个又会履行createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))

。 createCoroutineUnintercepted 这个会履行上面那个代码的create办法获取到MainActivityonCreateonCreate1,completion便是尖端协程,在当时协程履行后,即invokeSuspend办法履行完,会调用尖端协程的resumeWith办法。尖端协程的invokeSuspend办法履行完当时协程域的所有协程就完毕了。

intercepted()办法假如需求调度线程则会将协程包装成DispatchedContinuation,所有前提都预备好了会调用当时协程的resumeWith办法。

internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    public final override fun resumeWith(result: Result<Any?>) {
        var current = this
        var param = result
        while (true) {
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // 调用父协程的resumeWith的办法
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }
    //要履行的协程代码体
    protected abstract fun invokeSuspend(result: Result<Any?>): Any?

resumeWith里边有个死循环,履行完invokeSuspend 办法,回来为COROUTINE_SUSPENDED则需求挂起,挂起则直接调用return 退出当时办法,所以协程挂起也没多少奥秘便是return完毕当时办法去履行子协程,并把当时协程传给子协程,子协程resumeWith办法中,由于是while(true),在履行完自身invokeSuspend办法后把 current = completion ,又康复到当时协程履行当时协程的invokeSuspend办法。completion 假如不是BaseContinuationImpl则是尖端协程,尖端协程承继自AbstractCoroutine,所有子协程都是承继自SuspendLambda, SuspendLambda又是承继自BaseContinuationImpl。所以调用完尖端协程的completion.resumeWith(outcome),return当时协程域的协程就履行完了。

总结一下:resumeWith这个死循环要跳出只能是挂起当时协程或许是履行完尖端协程的resumeWith()办法。

再来说一下其他的几个协程中常用的api

delay 办法

这个办法便是用来挂起当时的协程的,并且支撑撤销挂起。可是delay办法挂起并不会堵塞主线程,由于这个内部经过另开一个线程合作DelayedTaskQueue行列来完成的,并不会影响主线程。

delay内部也是经过suspendCancellableCoroutine完成。

suspendCancellableCoroutine、suspendCoroutine

这两个办法会挂起当时协程,去履行耗时操作,当耗时操作履行完康复当时协程履行的时分就能够获取到suspendCancellableCoroutine、suspendCoroutine的回来值,所以一般用于和其他库做适配比如retrofit,留意这两个办法内部并不会开启子协程 。

retrofit 中运用如下

@JvmName("awaitNullable")
suspend fun <T : Any> Call<T?>.await(): T? {
    return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
            cancel()
        }
        enqueue(object : Callback<T?> {
            override fun onResponse(call: Call<T?>, response: Response<T?>) {
                if (response.isSuccessful) {
                    continuation.resume(response.body())
                } else {
                    continuation.resumeWithException(HttpException(response))
                }
            }
            override fun onFailure(call: Call<T?>, t: Throwable) {
                continuation.resumeWithException(t)
            }
        })
    }
}

enqueue 是异步办法,这里把异步处理完恳求后经过continuation.resume 系列办法回到当时协程,履行当时协程的invokeSuspend办法。

GlobalScope正确运用

假如在许多处经过GlobalScope.launch发动协程,这样会形成协程十分难办理,由于不能经过尖端域GlobalScope去撤销协程,并且这种办法发动的生命周期跟随应用的生命周期,十分容易形成内存走漏。

假如真要运用GlobalScope的话,能够把GlobalScope.launch发动协程的回来值job都保存在map中,自己办理这些job的状态,在协程需求撤销的时分从map移除job并调用其cancel办法。