前段時(shí)間在項(xiàng)目中引入了 Kotlin Coroutine令漂,那么也來談?wù)剬λ睦斫饴タ取K^窺一斑而知全豹娃弓,首先嘗試透過一個(gè)??來窺探協(xié)程的啟動流程辟汰,直接通過 GlobalScope.launch 啟動了一個(gè)頂級協(xié)程并指定在主線程中執(zhí)行:
fun test() {
GlobalScope.launch(Dispatchers.Main) {
Log.d("GlobalScopeTest", "Log in GlobalScope")
}
Log.d("GlobalScopeTest", "Log after GlobalScope")
}
上面的代碼輸出結(jié)果為:
Log after GlobalScope
Log in GlobalScope
為什么會出現(xiàn)這樣的執(zhí)行順序列敲?協(xié)程體內(nèi)的代碼邏輯是透過什么機(jī)制分發(fā)到主線程執(zhí)行?要解答這些問題帖汞,還得從 GlobalScope.launch 尋找突破口戴而。
1、GlobalScope.launch:協(xié)程入口
launch 是個(gè)擴(kuò)展函數(shù)翩蘸、啟動協(xié)程并且返回一個(gè) job 對象所意。這里先大概了解返回的 job 可以方便地對協(xié)程進(jìn)行追蹤,取消等操作催首。
launch 源碼:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
// 構(gòu)建 AbstractCoroutine 對象扶踊,并由此開啟協(xié)程任務(wù)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
看了 launch 方法,會有一個(gè)疑惑:經(jīng)過編譯后郎任,協(xié)程把我們的代碼邏輯封裝成了一個(gè) SuspendLambda 類秧耗,那么必然需要該類的相關(guān)信息才可能進(jìn)行回調(diào)執(zhí)行邏輯,但是在入口處并沒有看到相關(guān)參數(shù)涝滴,難道還有什么黑科技能夠獲取到類相關(guān)信息绣版?
事實(shí)上反編譯 class 文件,發(fā)現(xiàn) CoroutineScope.launch 方法簽名和源碼實(shí)現(xiàn)略有不同歼疮,其中多了一個(gè)參數(shù):Function2<? super CoroutineScope, ? super Continuation<? super T>, ? extend Object>杂抽。 而生成的 SuspendLambda 類就是 Function2 的實(shí)現(xiàn)類。
沒錯(cuò)韩脏,在調(diào)用 CoroutineScope.launch 的時(shí)候缩麸,傳入的 Function2 的參數(shù)值就是生成的 SuspendLambda 對象!所以在這里并沒有什么黑科技赡矢,只是由于編譯器的參與杭朱,隱藏了其中一些細(xì)節(jié)。
反編譯得到的 launch 源碼:
@NotNull
public static final Job launch(@NotNull CoroutineScope $this$launch, @NotNull CoroutineContext context, @NotNull CoroutineStart start, @NotNull Function2<? super CoroutineScope, ? super Continuation<? super Unit>, ? extends Object> block) {
StandaloneCoroutine coroutine;
Intrinsics.checkParameterIsNotNull($this$launch, "$this$launch");
Intrinsics.checkParameterIsNotNull(context, "context");
Intrinsics.checkParameterIsNotNull(start, "start");
Intrinsics.checkParameterIsNotNull(block, "block");
CoroutineContext newContext = CoroutineContextKt.newCoroutineContext($this$launch, context);
if (start.isLazy()) {
coroutine = new LazyStandaloneCoroutine(newContext, block);
} else {
coroutine = new StandaloneCoroutine(newContext, true);
}
coroutine.start(start, coroutine, block);
return coroutine;
}
回到 launch 吹散,方法最后調(diào)用 start 啟動了協(xié)程弧械。層層追蹤最后調(diào)用了 startCoroutineCancellable:
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellable(Unit)
}
觀察方法名大概能得出一個(gè)判斷:
- createCoroutineUnintercepted:首先創(chuàng)建了一個(gè)非攔截的協(xié)程 Continuation。
- intercepted:添加攔截器 Continuation空民。
- resume:正式啟動協(xié)程刃唐。
看來要知道更多細(xì)節(jié)羞迷,還要從這幾個(gè)方法入手。
2画饥、createCoroutineUnintercepted:構(gòu)建任務(wù)層對象
createCoroutineUnintercepted 在 jvm 的實(shí)現(xiàn)在 IntrinsicsJvm 類中衔瓮。在這里,launch 處傳入的 Function2 參數(shù)起了作用抖甘,通過這個(gè)對象調(diào)用 create() 來構(gòu)建 編譯器生成的 SuspendLambda 對象热鞍。
@SinceKotlin("1.3")
public fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
// 創(chuàng)建 SuspendLambda 對象
create(receiver, probeCompletion)
else {
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
反編譯得到的 create 方法 :
@NotNull
public final Continuation<Unit> create(@Nullable Object value, @NotNull Continuation<?> completion) {
Intrinsics.checkParameterIsNotNull(completion, "completion");
// 將Continuation傳入作為completion參數(shù)值(靜態(tài)代理)
Continuation coroutineTest$test$1 = new CoroutineTest$test$1(this.$context, completion);
CoroutineScope coroutineScope = (CoroutineScope) value;
coroutineTest$test$1.p$ = (CoroutineScope) value;
return coroutineTest$test$1;
}
所以 launch 傳入的 Function2 參數(shù)作用是構(gòu)建出編譯器為我們生成的 SuspendLambda 對象,后續(xù)才能回調(diào)掛起的邏輯衔彻。那么 SuspendLambda.intercepted() 又做了什么薇宠?
3、Continuation.intercepted:構(gòu)建任務(wù)調(diào)度層對象
追蹤 intercepted 調(diào)用鏈艰额,最終調(diào)用了 ContinuationImpl.intercepted :
public fun intercepted(): Continuation<Any?> =
intercepted ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
上面代碼關(guān)鍵點(diǎn)就在于 (context[ContinuationInterceptor]?.interceptContinuation(this)昼接,context[ContinuationInterceptor] 得到的是 HandlerContext 對象(why?)而 handlerContext 是 CoroutineDispatcher 的子類(CoroutineDispatcher 可以理解為線程級別的任務(wù)調(diào)度類悴晰,決定了任務(wù)在哪個(gè)線程中執(zhí)行)慢睡,CoroutineDispatcher.interceptContinuation() 返回了 DispatchedContinuation 對象。
why HandlerContext handlerContext = context[ContinuationInterceptor] ?
栗子中我們調(diào)用 launch 傳入了 Dispatchers.Main 作為 context铡溪,CoroutineContext 可以理解為一個(gè)數(shù)據(jù)結(jié)構(gòu)漂辐,其中鍵為 ContinuationInterceptor,值為 Dispatchers.Main棕硫。所以context[ContinuationInterceptor] 得到的是 Dispatchers.Main髓涯,即為 HandlerContext。
intercepted 就是通過靜態(tài)代理得到了 DispatchedContinuation 對象哈扮。接下來只需要通過 resumeCancellable 開啟協(xié)程纬纪。
4、DispatchedContinuation.resumeCancellable:開啟協(xié)程任務(wù)
DispatchedContinuation 為任務(wù)調(diào)度類滑肉,將具體任務(wù)分配給 Dispatcher(也就是上面通過 context[ContinuationInterceptor] 得到的對象) 執(zhí)行包各。resumeCancellable 內(nèi)部調(diào)用 dispatcher.dispatch 將任務(wù)交給了 Dispatcher,方法第二個(gè)參數(shù) this 指代 runnable 對象靶庙。
resumeCancellable 的實(shí)現(xiàn):
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
inline fun resumeCancellable(value: T) {
// 指定需要線程調(diào)度问畅,默認(rèn)為true
if (dispatcher.isDispatchNeeded(context)) {
_state = value
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)
} else {
// 不需要線程調(diào)度
executeUnconfined(value, MODE_CANCELLABLE) {
if (!resumeCancelled()) {
resumeUndispatched(value)
}
}
}
}
HandlerContext 的 dispatch 方法實(shí)現(xiàn)如下,通過 handler 把任務(wù)拋出去:
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
DispatchedContinuation 內(nèi) runnable.run 方法實(shí)現(xiàn)如下:
public final override fun run() {
val taskContext = this.taskContext
var exception: Throwable? = null
try {
val delegate = delegate as DispatchedContinuation<T>
val continuation = delegate.continuation
val context = continuation.context
val job = if (resumeMode.isCancellableMode) context[Job] else null
val state = takeState() // NOTE: Must take state in any case, even if cancelled
withCoroutineContext(context, delegate.countOrElement) {
if (job != null && !job.isActive) {
val cause = job.getCancellationException()
cancelResult(state, cause)
continuation.resumeWithStackTrace(cause)
} else {
val exception = getExceptionalResult(state)
if (exception != null)
continuation.resumeWithStackTrace(exception)
else
// 回調(diào)suspendLambda.resumeWith()
continuation.resume(getSuccessfulResult(state))
}
}
} catch (e: Throwable) {
// This instead of runCatching to have nicer stacktrace and debug experience
exception = e
} finally {
val result = runCatching { taskContext.afterTask() }
handleFatalException(exception, result.exceptionOrNull())
}
}
run 方法內(nèi)部通過調(diào)用 continuation.resume 回調(diào)整條鏈路的任務(wù)邏輯六荒。
continuation.resume 的主要下游調(diào)用鏈路:continuation.resume() -> continuation.resumeWith() -> suspendLambda.invokeuspend() -> AbstractContinue.resumeWith()护姆。
其中 suspendLambda.invokeuspend 內(nèi)部封裝的就是我們栗子中的邏輯: Log.d("GlobalScopeTest", "Log in GlobalScope")
從 launch 開啟協(xié)程到如何回調(diào)到我們的邏輯大致流程已經(jīng)理清。由于這個(gè)栗子指定了主線程作為運(yùn)行環(huán)境掏击。還有更多關(guān)于協(xié)程如何進(jìn)行線程調(diào)度卵皂、多線程下函數(shù)掛起、恢復(fù)等流程在這里未涉及到砚亭。不過我們大概可以推測對于指定IO線程執(zhí)行的協(xié)程體灯变,協(xié)程必然維護(hù)了自己的線程池作為協(xié)程的運(yùn)行環(huán)境豺旬。
小結(jié)
- launch 方法還有一個(gè)隱式參數(shù):Function2,也就是編譯器生成 SuspendLambda 的子類對象柒凉。為后續(xù)構(gòu)建真實(shí)可用的 SuspendLambda 對象做鋪墊。
- 通過靜態(tài)代理構(gòu)建了三層 Continuation 分別負(fù)責(zé)不同的職責(zé):AbstractCoroutine(協(xié)程狀態(tài)處理層) -> SuspendLambda(邏輯執(zhí)行層) -> CoroutineDispatcher(任務(wù)調(diào)度層)
- 任務(wù)最終交由 CoroutineDispatcher 執(zhí)行篓跛。最終脫離不開線程級別的任務(wù)調(diào)度膝捞。