本文最后更新于 884 天前,其中的信息可能已经有所发展或是发生改变。
1. 基本认识
Kotlin 的编译器检测到 suspend 关键字修饰的函数以后,会自动将挂起函数转换成带有 CallBack 的函数
suspend fun getUserInfo(): String {
withContext(Dispatchers.IO) {
delay(1000L)
}
return "BoyCoder"
}
// Continuation 等价于 CallBack
// ↓
public static final Object getUserInfo(Continuation $completion) {
...
return "BoyCoder";
}
重点是这个图片,后面分析挂起函数内多个挂起函数,理解实现状态机切换相关回调
前后suspend ()->String 变成了 (Continuation)-> Any?
由于 suspend 修饰的函数,既可能返回 CoroutineSingletons.COROUTINE_SUSPENDED,也可能返回实际结果”no suspend”,甚至可能返回 null,为了适配所有的可能性,CPS 转换后的函数返回值类型就只能是 Any?了。
2. 反编译后分析
suspend fun testCoroutine() {
log("start")
val user = getUserInfo()
log(user)
val friendList = getFriendList(user)
log(friendList)
val feedList = getFeedList(friendList)
log(feedList)
}
// 没了 suspend,多了 completion
fun testCoroutine(completion: Continuation<Any?>): Any? {}
// 注意上图 表示 completion对应的是什么,实现 invoke回调 从而执行后续的 挂起函数
fun getUserInfo(completion: Continuation<Any?>): Any?{}
fun getFriendList(user: String, completion: Continuation<Any?>): Any?{}
fun getFeedList(friendList: String, completion: Continuation<Any?>): Any?{}
在 testCoroutine 函数里,会多出一个 ContinuationImpl 的子类,它的是整个协程挂起函数的核心
fun testCoroutine(completion: Continuation<Any?>): Any? {
// 初始化判断是否有 ‘回调’实例,防止重复创建
val continuation = if (completion is TestContinuation) {
completion
} else {
// 作为参数
// ↓
TestContinuation(completion)
}
// completion参数 对应?说的那个图
class TestContinuation(completion: Continuation<Any?>?) : ContinuationImpl(completion) {
// 表示协程状态机当前的状态
var label: Int = 0
// 协程返回结果
var result: Any? = null
// 用于保存之前协程的计算结果
var mUser: Any? = null
var mFriendList: Any? = null
// invokeSuspend 是协程的关键
// 它最终会调用 testCoroutine(this) 开启协程状态机
// 状态机相关代码就是后面的 when 语句
// 协程的本质,可以说就是 CPS + 状态机
override fun invokeSuspend(_result: Result<Any?>): Any? {
result = _result
label = label or Int.Companion.MIN_VALUE
return testCoroutine(this)
}
}
// 三个变量,对应原函数的三个变量
lateinit var user: String
lateinit var friendList: String
lateinit var feedList: String
// result 接收协程的运行结果
var result = continuation.result
// suspendReturn 接收挂起函数的返回值
var suspendReturn: Any? = null
// CoroutineSingletons 是个枚举类
// COROUTINE_SUSPENDED 代表当前函数被挂起了
val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED
when (continuation.label) {
0 -> {
// 检测异常
throwOnFailure(result)
log("start")
// 将 label 置为 1,准备进入下一次状态
continuation.label = 1
// 执行 getUserInfo
suspendReturn = getUserInfo(continuation)
// 判断是否挂起
if (suspendReturn == sFlag) {
return suspendReturn
} else {
result = suspendReturn
//go to next state 会使用到label相关的 goto语法,仅仅使用suspend进行修饰,并没有相应的协程域-“伪挂起函数” 会走到这里
}
}
1 -> {
throwOnFailure(result)
// 获取 user 值
user = result as String
log(user)
// 将协程结果存到 continuation 里
continuation.mUser = user
// 准备进入下一个状态
continuation.label = 2
// 执行 getFriendList
suspendReturn = getFriendList(user, continuation)
// 判断是否挂起
if (suspendReturn == sFlag) {
return suspendReturn
} else {
result = suspendReturn
//go to next state
}
}
2 -> {
throwOnFailure(result)
user = continuation.mUser as String
// 获取 friendList 的值
friendList = result as String
log(friendList)
// 将协程结果存到 continuation 里
continuation.mUser = user
continuation.mFriendList = friendList
// 准备进入下一个状态
continuation.label = 3
// 执行 getFeedList
suspendReturn = getFeedList(friendList, continuation)
// 判断是否挂起
if (suspendReturn == sFlag) {
return suspendReturn
} else {
result = suspendReturn
//go to next state
}
}
3 -> {
throwOnFailure(result)
user = continuation.mUser as String
friendList = continuation.mFriendList as String
feedList = continuation.result as String
log(feedList)
loop = false
}
}
}
- when 表达式实现了协程状态机
- continuation.label 是状态流转的关键
- continuation.label 改变一次,就代表协程切换了一次
- 每次协程切换后,都会检查是否发生异常
- testCoroutine 里的原本的代码,被拆分到状态机里各个状态中,分开执行
- getUserInfo(continuation),getFriendList(user, continuation),getFeedList(friendList, continuation) 三个函数调用传的同一个 continuation 实例。
- 一个函数如果被挂起了,它的返回值会是:CoroutineSingletons.COROUTINE_SUSPENDED
- 切换协程之前,状态机会把之前的结果以成员变量的方式保存在 continuation 中。