kotlin協程suspend關鍵字源碼解析(可能有點亂,順着分析順着寫點,將就看特別是看注釋說明,相信還是有點收獲的)
//1. 自己編寫的kotlin源代碼
private val mainScope = MainScope()
fun xSuspend(view: View) {
mainScope.launch {
//coroutine開始
Log.d("Suyf", "xSuspend: 111111111111111" + Thread.currentThread())
val result = loginRepository.makeLoginRequest2()
Log.d("Suyf", "xSuspend: 4444444444444444" + Thread.currentThread())
if (TextUtils.isEmpty(result)) {
showToast("Error:${result}")
} else {
showToast("Success:${result}")
}
//coroutine結束
}
Log.d("Suyf", "xSuspend: 555555555555555555555" + Thread.currentThread())
}
private fun showToast(text: String) {
Toast.makeText(this@CoroutineActivity, text, Toast.LENGTH_SHORT).show()
}
//1. 編譯器最后生成的代碼
public final void xSuspend(View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
Job unused = BuildersKt__Builders_commonKt.launch$default(this.mainScope, null, null, new CoroutineActivity$xSuspend$1(this, null), 3, null);
Log.d("Suyf", "xSuspend: 555555555555555555555" + Thread.currentThread());
}
/* access modifiers changed from: private */
public final void showToast(String text) {
Toast.makeText(this, text, 0).show();
}
//1. 協程恢復后要執行的回調方法invokeSuspend
public final class CoroutineActivity$xCoroutineScope$1 extends SuspendLambda{
public final Object invokeSuspend(Object $result) {
//這里代碼邏輯等價於上面[coroutine開始]到[coroutine結束]的代碼邏輯
}
}
//1. 分析:編譯器會自動生成一個類CoroutineActivity$xSuspend$1,將上面的[coroutine開始]到[coroutine結束]的代碼也就是協程回調的代碼塊放進invokeSuspend方法,然后切換到子線程執行完了,再切換恢復到主線程,執行這個類的invokeSuspend方法。
//2. 自己編寫的kotlin源代碼,帶有suspend關鍵字。LoginRepository類的makeLoginRequest2方法如下:
suspend fun makeLoginRequest2(): String? {
Log.d("Suyf", "xSuspend: 2222222222222222" + Thread.currentThread())
return withContext(Dispatchers.IO) {
try {
Log.d("Suyf", "xSuspend: 3333333333333333" + Thread.currentThread())
val url = URL(loginUrl)//網絡請求,耗時操作
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "GET"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
return@withContext readInputStream(inputStream)//讀取stream為字符串的普通方法
}
} catch (e: Exception) {
e.printStackTrace()
}
return@withContext null
}
}
//2. 編譯后生成的源代碼,自動生成LoginRepository$makeLoginRequest2$2類
// 自動添加continuation參數,實現回調功能。也就是說suspend掛起和恢復其實也是回調,
// 只是編譯器幫我們生成這些回調代碼了。。。
public final Object makeLoginRequest2(Continuation<? super String> continuation) {
Log.d("Suyf", "xSuspend: 2222222222222222" + Thread.currentThread());
return BuildersKt.withContext(Dispatchers.getIO(), new LoginRepository$makeLoginRequest2$2(this, null), continuation);
}
//2. LoginRepository$makeLoginRequest2$2類繼承SuspendLambda 並實現了invoke和invokeSuspend方法
// 注意1編譯器會為每一個suspend掛起函數生成一個類繼承SuspendLambda,並實現invoke和invokeSuspend方法
// 注意:編譯時每遇到suspend關鍵字都是注意1這樣,那如果大量使用suspend關鍵字,無形中新增了很多類和方法。。
public final class LoginRepository$makeLoginRequest2$2 extends SuspendLambda{
//供Dispatchers.getIO()線程調用
@Override // kotlin.jvm.functions.Function2
public final Object invoke(CoroutineScope coroutineScope, Continuation<? super String> continuation) {
return create(coroutineScope, continuation).invokeSuspend(Unit.INSTANCE);
}
@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
public final Object invokeSuspend(Object $result) {
//具體的業務實現,可以是耗時操作
}
}
- 那上面的Dispatchers.getIO()是個啥??就是一個IO子線程。。。就是我們平時說的子線程。。。
//LimitingDispatcher繼承Executor本身就是一個Executor
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
val IO: CoroutineDispatcher = LimitingDispatcher(
this,
systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
"Dispatchers.IO",
TASK_PROBABLY_BLOCKING
)
}
- BuildersKt.withContext()方法做了什么?是怎么切換線程的??
//第一步:在BuildersKt類里withContext方法
BuildersKt.withContext() -> BuildersKt__Builders_commonKt.withContext()
->最后是return suspendCoroutineUninterceptedOrReturn(),可以在該方法下斷點調試。
//第二步:創建DispatchedCoroutine
val coroutine = DispatchedCoroutine(newContext, uCont)
coroutine.initParentJob()
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
//第三步:其實最關鍵是startCoroutineCancellable方法,里面進行了線程切換,
//通通過狀態機機制block與loop,最后切換回調用線程。。。
fun resumeCancellableWith(result:Result<T>,onCancellation:((cause: Throwable)->Unit)?){
val state = result.toState(onCancellation)
if (dispatcher.isDispatchNeeded(context)) {//是否需要轉發
_state = state
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)//轉發,通過handler切換線程
} else {
//...
}
}
}
//第四步:通過handler切換線程
class HandlerContext:HandlerDispatcher(){
//block即DispatchedContinuation[Dispatchers.Main, Continuation at xxx.invokeSuspend()]
//可見這個block就是主線程上的一個Continuation,也就是一個回調
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
}
//第五步:執行resume恢復,最后執行編譯器為我們自動生成的invokeSuspend方法,回到開始點的地方即恢復。
class DispatchedTask<in T>() : SchedulerTask() {
public final override fun run() {
//...
continuation.resume(getSuccessfulResult(state))
}
}
總結1:
- Job類:啟動協程的兩種方式即launch和async。launch是啟動直接執行,async是啟動后需要await觸發執行。啟動協程返回的結果就是一個Job,可以通過job取消協程等等操作。
- withContext方法:線程切換,注意是切換執行協程的線程,也就是說指定在哪個線程執行協程代碼塊。
- CoroutineDispatcher類:協程分發器,將協程分發到哪個線程去執行,配合withContext方法使用。
- CoroutineContext類:協程上下文,用於暫停或者恢復協程時,保存和恢復現場等場景。
- CoroutineScheduler類:協程執行器,內部實現就是使用的我們熟悉的線程池Executor。
- CoroutineScope類:跟蹤使用
launch或async創建的所有協程。可以調用scope.cancel()以取消正在進行的同一Scope的工作Job(即正在運行的協程),簡單來說就是管理一組Job的。
總結2:
- 協程執行耗時操作時也是依靠子線程去完成的,從子線程切換回UI線程也是依靠我們平時接觸的Android Handler類完成的。
- 我們寫的協程代碼看起來是同步形式的,其實也是異步回調的,只是編譯器幫我們自動生成了回調代碼而已。編譯器將suspend形式的同步代碼,生成帶有Continuation回調形式的代碼。
總結3:
- 雖然說協程內部也是協助線程池和Android Handler完成子線程和UI線程的切換。但是為了充分利用線程資源和減少線程切換,內部也維護着自己的調用棧,所以出現CoroutineContext、CoroutineScope等新的概念。
- 內部源代碼實現還是有點復雜的,暫時看得不是很懂,有空再看看。。。
