kotlin协程suspend关键字源码解析
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等新的概念。
- 内部源代码实现还是有点复杂的,暂时看得不是很懂,有空再看看。。。