考虑这样一个故事(场景),我们要从网络上下载图片并展示
kotlin
fun display(url: String) {
val data = download(url)
val image = decode(data)
show(image)
}
download 和 decode 是耗时操作,不能在 Ui 线程中执行
所以修改 download 和 decode,让它们异步工作,完成后通过 callback 回调
这里特意把 callback 定义成和 Kotlin 中同样的名字 Continuation
kotlin
fun display(url: String) {
download(url) {
decode(image) {
show(image)
}
}
}
fun download(url: String, continuation: Continuation) {}
fun decode(data: ByteArray, continuation: Continuation) {}
我们优化一下上面这种回调写法,实际上也是 Kotlin 协程框架采用的方案:
kotlin
fun display(url: String) {
val continuation = object: Continuation {
var label = 0
var result: Any? = null
fun resumeWith(result: Any) {
}
}
when (continuation.label) {
0 -> {
continuation.label = 1
download(url, continuation)
return
}
1 -> {
continuation.label = 2
decode(continuation.result as ByteArray, continuation)
return
}
}
display(continuation.result as Image)
}
- continuation 的 label 字段标识不同的阶段
- when 执行display 函数不同的阶段/部分
- download 和 decode 执行完毕后调用 continuaton 的 resumeWith 方法通知调用者
这里有个问题,resumeWith 咋实现? 我们需要修改方案吗?比如把 when代码快挪到 resumeWith 里头? Kotlin 协程框架采用的方法是,给 display 增加一个 continuation 参数,然后递归调用!
kotlin
fun display(url: String, continuation: Continuation? = null) {
// 这里有个调用约定,开始时 continuation 为 null
val continuation = continuation ?: object: Continuation {
var label = 0
var result: Any? = null
fun resumeWith(result: Any) {
continuation.result = result
// 递归调用
display(url, continuation)
}
}
...
}
当然这个 continuation 参数另有作用!
扩展一下这个场景,display 后,我们想在 display 之后干点什么!
你肯定会想那简单啊,我们已经有 continuation了!
kotlin
fun page() {
val continuation = object: Continuation {
fun rewumeWith(result: Any?) {
// 干点什么~
}
}
display(url, continuation)
}
问题又来了,之前我们约定调用 display 作为入口调用的时候 continuation 需要传 null...
这里也不卖关子了,Kotlint 的解决方案是类型判断!
kotlin
fun display(url: String, completion: Continuation? = null) {
var $continuation: Continuation? = null
if (continuation is ContinuationImpl) {
// 自己人!(递归调用)
} else {
// 异乡人!,注意这里保存了 completion
$continuation = ContinuationImpl(completion)
}
// 定义一个内部类
inner class ContinuationImpl(
completion: Continuation
): Continuation {
fun resumeWith(result: Any?) {
...
}
}
...
show(...)
continuation?.resumeWith(...)
}
好了,故事讲完了,回到现实中
这个故事的 suspend 函数版本如下
kotlin
suspend fun display(url: String) {
val data = download()
val image = decode(data)
show(image)
}
suspend fun download(url: String) {}
suspend fun decode(data: ByteArray) {}
感谢 Kotlin 协程~