1.簡單啟動協程Demo
本文例子代碼地址:gitee.com/mcaotuman/k…
寫個小demo,先記住怎么用,后面再分析原始碼,

三個掛起函式:

輸出結果:

2.構建suspend修飾的lambda函式
2.1 suspend lambda
下面這段代碼是suspend修飾lambda運算式:
val mySuspendLambda: suspend () -> String = {
//回傳一個字串hello world
val one = commonSuspendFun()
//回傳一個字串hello world2
val two = commonSuspendFun2()
//回傳一個字串hello world3
val three = commonSuspendFun3()
one+two+three
}
suspend lambda反編譯成Java代碼究竟長什么樣子呢?

我們用JEB工具反編譯看看:
我們根據代碼定位到包three下

按右鍵決議位元組碼檔案,得到反編譯之后的Java代碼:

由上圖我們知道suspend lambda經過編譯器的黑魔法,會編譯成SuspendLambda類(very important),繼承關系如下圖:

現在你看到BaseContinuationImpl類和Continuation介面,可能會一臉懵逼,究竟是干嘛的?沒事,我們下面繼續分析,
2.2 suspend函式
下面這段代碼是suspend函式:
suspend fun commonSuspendFun(): String {
return "[hello world] "
}
反編譯成Java代碼長成這個樣子:

沒了suspend關鍵字,多了一個Continuation型別的引數,
這個Continuation又出現了,上面小節提到SuspendLambda會實作Continuation介面
Continuation到底是個什么東西?
來,看看原始碼:

這個Continuation介面不就是類似我們常寫的CallBack嗎?Continuation的resumeWith方法不就是相當于CallBack的onSuccess嗎?沒錯的,
這個從掛起函式轉換成CallBack 函式的程序,被稱為:CPS 轉換(Continuation-Passing-Style Transformation),
不知道你有沒有發現,回傳值由原來的String 變成 Object!
由于 suspend 修飾的函式,既可能回傳 CoroutineSingletons.COROUTINE_SUSPENDED,也可能回傳實際結果[hello world],甚至可能回傳 null,為了適配所有的可能性,CPS 轉換后的函式回傳值型別就只能是 Object了,
3.創建協程:createCoroutine
我在如何查看Kotlin expect關鍵字在對應平臺的實作(actual)? 這篇文章也曾提過createCoroutine的原始碼,他最后會調到createCoroutineUnintercepted方法里,

根據上面的分析,我們知道suspend lambda在編譯器的黑魔法編譯之后,會變成SuspendLambda類,實際上也是繼承BaseContinuationImpl,那么他會直接走create方法,
實際上,是走到SuspendLambda類的建構式

最后,走到BaseContinuationImpl,把SuspendLambda構建出來,

4.啟動協程:resume
由步驟3得知,我們已經拿到了SuspendLambda實體,現在我們呼叫resume擴展函式啟動協程,

實際上是呼叫Continuation的resumeWith方法
那么我們debug一下BaseContinuationImpl的resumeWith方法,就知道他是怎么啟動協程的了,

**CoroutineRunKt $ testFunGetContinuation $ mySuspendLambda $1**對應suspend lambda物件的
mySuspendLambda.這個物件主要作用就是用來維護 狀態機,這個也是kotlin 協程的關鍵,
我們跟蹤一下**CoroutineRunKt$testFunGetContinuation$mySuspendLambda$1**的Java代碼:

jeb demo版不允許復制代碼,蛋疼,只能截圖,
于是我手動打上了代碼,并注釋關鍵步驟:
public final Object invokeSuspend(Object arg8) {
String v3;
CoroutineRunKt.testFunGetContinuation.mySuspendLambda.1 this;
// v0:掛起函式回傳標識SUSPEND_FLAG
Object v0 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
// (一)label一開始默認是0
switch (this.label) {
case 0:
ResultKt.throwOnFailure(arg8);
// (二)將label設定為1
this.label = 1;
// (三)呼叫第一個掛起函式,傳入自己作為引數
// 如果回傳SUSPEND_FLAG就回傳函式,
// 當掛起函式用傳入的Continuation 呼叫resume時候又重新回到這個invokeSuspend方法
// 當然我們這里由于不會回傳SUSPEND_FLAG 所以繼續向下運行
Object v2 = CoroutineRunKt.commonSuspendFun((Continuation)this);
if (v2 == v0) {
return v0;
}
this = this;
// 程式跳轉到label_48
goto label_48;
case 1:
ResultKt.throwOnFailure(arg8);
this = this;
label_48:
this.L$0 = one;
// (四)將label設定為2
this.label = 2;
// (五) 呼叫掛起函式 與(三)相同
Object v3_1 = CoroutineRunKt.commonSuspendFun2((Continuation)this);
if (v3_1 == v0) {
return v0;
}
v3 = one;
arg8 = v3_1;
// (六) 走到case 2
label_60:
String two = (String)arg8;
this.L$0 = v3;
this.L$1 = two;
// (八)將label設定為3
this.label = 3;
// (九)呼叫最后一個掛起函式 同樣與(三)一樣檢查結果
Object v4 = CoroutineRunKt.commonSuspendFun3((Continuation)this);
// (十)將掛起函式一、二、三回傳的結果相加回傳
return v4 = v0 ? v0 : v3 +two +((String)v4);
case 2:
String v2_2 = (String) this.L$0;
ResultKt.throwOnFailure(arg8);
v3 = v2_2;
this = this;
// (七) 走到label_60
goto label_60;
case 3:
Stirng v2_3 = (Stirng)this.L$1;
Stirng v1 = (Stirng)this.L$0;
ResultKt.throwOnFailure(arg8);
return v1 + v2_3 +((Stirng)arg8);
default:
throw new IllgelStateException("call to 'resume' befor 'invoke' with coroutinue");
}
}
看位元組碼反編譯之后的代碼,是不是有點頭疼?我也覺得,我寫了一個Java版的狀態機實作Demo幫助大家理解,
代碼地址:gitee.com/mcaotuman/k…


switch case運算式實作了協程的狀態機模式,continuation.label是狀態機的標志位,每改變一次,label加1,協程就切換1次, 一個函式如果被掛起了,它的回傳值會是: CoroutineSingletons.COROUTINE_SUSPENDED,函式恢復后會繼續當前label執行下去,直到所有label執行完,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/297871.html
標籤:其他
