Kotlin 协程源代码泛读:Continuation 思想实验

考虑这样一个故事(场景),我们要从网络上下载图片并展示

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 协程~

相关推荐
人生游戏牛马NPC1号44 分钟前
学习 Flutter (三):玩安卓项目实战 - 上
android·学习·flutter
小馬佩德罗2 小时前
Android系统的问题分析笔记 - Android上的调试方式 debuggerd
android·调试
清霜之辰4 小时前
安卓基于 FirebaseAuth 实现 google 登录
android·google·auth·firebase
GitLqr4 小时前
数码洞察 | Apple VS DMA、三星新品、Android 16KB Page Size
android·ios·samsung
alexhilton4 小时前
SnapshotFlow还是collectAsState?对于Jetpack Compose来说哪个更香?
android·kotlin·android jetpack
Erwooow5 小时前
Android 16k jni修改
android
l软件定制开发工作室5 小时前
基于Android的景点旅游信息系统App
android
张可6 小时前
一个KMP/CMP项目的组织结构和集成方式
android·前端·kotlin
林林要一直努力6 小时前
AOSP Settings模块问题初窥
android·学习·bug·android studio
顾林海6 小时前
Android 性能优化:启动优化全解析
android·java·面试·性能优化·zygote