本文字数:12447字
预计阅读时间:32分钟
源码分析-Kotlin中协程的挂起和恢复
前言
Kotlin中的协程经过几个版本的升级已经非常成熟了,但是协程的概念目前没有一个明确且被普遍接受的定义。究其根源无论我们怎么去理解协程的概念,它最核心的点就是函数或者一段程序能够被挂起,稍后再挂起的位置恢复。所以在任何场景下探讨协程都能够落脚到挂起和恢复。本文通过源码对协程创建->挂起->恢复流程进行分析解读。希望能够帮助大家对Kotlin协程的理解起到帮助。
协程的创建
Kotlin中协程是复合协程,是为了方便开发者使用而进一步封装的API,当我们在分析的时候无从下手就是因为经过封装的协程在经过编译后才能看到它的庐山真面目。下面我们通过构造一个简单的协程并反编译成java代码查看。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startCoroutine()
}
private fun startCoroutine() {
val coroutine: suspend CoroutineScope.() -> Unit = {
cumpute1()
cumpute2()
}
GlobalScope.launch(block = coroutine)
}
suspend fun cumpute1() {
print("cupmpute1")
}
suspend fun cumpute2() {
print("cupmpute1")
}
}
coroutine
属性是 suspend CoroutineScope.() -> Unit
函数类型对象,反编译成java代码
final class MainActivity$startCoroutine$coroutine$1 extends SuspendLambda implements Function2 {
//状态机初始值0
int label;
public final Object invokeSuspend(Object $result) {
Object coroutine_suspend = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
...
//将label置为1 开始调用挂起函数cumpute1
this.label = 1;
if (MainActivity.this.cumpute1(this) == coroutine_suspend) {
//如果函数被挂起返回挂起标识
return coroutine_suspend;
}
break;
...
}
//将label置为2 开始调用挂起函数cumpute2
this.label = 2;
if (MainActivity.this.cumpute2(this) == coroutine_suspend) {
return coroutine_suspend;
} else {
return Unit.INSTANCE;
}
}
public final Continuation create(Object value,Continuation completion) {
...
//创建并返回一个Continuation对象
MainActivity$startCoroutine$coroutine$1 coroutine = new
MainActivity$startCoroutine$coroutine$1(completion);
return coroutine;
}
public final Object invoke(Object var1, Object var2) {
return ((MainActivity$startCoroutine$coroutine$1)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
}
}
通过反编译代码可以看到声明的 coroutine
转换成了继承 SuspendLambda
的类,可以称之为协程体类。内部实现了两个方法
invokeSuspend()
内部是通过label状态来控制调用流程.create()
方法接收一个Continuation
对象,然后创建并返回协程体类对象。
public final Object cumpute1(Continuation completion) {
...
return Unit.INSTANCE;
}
suspend
修饰的函数经过反编译后额外接收了一个 Continuation
类型参数,这也就是为什么普通函数内不能调用 suspend
修饰的函数的原因。附上 SuspendLambda
的类图
从类图上可以看出该类的承链 SuspendLambda -> ContinuationImpl -> BaseContinuationImpl -> Continuation
Continuation
是一个接口定义了resumeWith(result:Result)
方法 和CoroutineContext
上下文属性BaseContinuationImpl
是一个抽象类实现了resumeWith( result : Result )
方法 ,并声明抽象方法invokeSuspend()
和create()
方法。ContinuationImpl
继承BaseContinuationImpl
构造方法接收Continuation
类型参数,内部实现intercepted()
方法将原始协程体类对象拦截包装,添加调度器实现并返回新的协程体类对象DispatchedContinuation
。
协程的挂起
接上面创建的协程代码,以 launch()
函数作为入口开始分析协程是如何挂起的。launch()
函数是 CoroutineScope
的一个扩展实现
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineSta