📚 如果您是 Android 平台上协程的初学者,请查阅下面这些协程 codelab:
协程 101
协程简化了 Android 平台的异步操作。正如官方文档《利用 Kotlin 协程提升应用性能》所介绍的,我们可以使用协程管理那些以往可能阻塞主线程或者让应用卡死的异步任务。
《利用 Kotlin 协程提升应用性能》
https://developer.android.google.cn/kotlin/coroutines
协程也可以帮我们用命令式代码替换那些基于回调的 API。例如,下面这段使用了回调的异步代码:
// 简化的只考虑了基础功能的代码fun loginUser(userId: String, password: String, userResult: Callback<User>) {// 异步回调userRemoteDataSource.logUserIn { user ->// 成功的网络请求userLocalDataSource.logUserIn(user) { userDb ->// 保存结果到数据库userResult.success(userDb)}}}
suspend fun loginUser(userId: String, password: String): User {val user = userRemoteDataSource.logUserIn(userId, password)val userDb = userLocalDataSource.logUserIn(user)return userDb}
Suspend 的工作原理
suspend fun loginUser(userId: String, password: String): User {val user = userRemoteDataSource.logUserIn(userId, password)val userDb = userLocalDataSource.logUserIn(user)return userDb}// UserRemoteDataSource.ktsuspend fun logUserIn(userId: String, password: String): User// UserLocalDataSource.ktsuspend fun logUserIn(userId: String): UserDb
有限状态机
https://en.wikipedia.org/wiki/Finite-state_machine
Continuation 接口
挂起函数通过 Continuation 对象在方法间互相通信。Continuation 其实只是一个具有泛型参数和一些额外信息的回调接口,稍后我们会看到,它会实例化挂起函数所生成的状态机。
Continuation
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-continuation/index.html
Continuation
https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/coroutines/Continuation.kt
我们先来看看它的声明:
interface Continuation<in T> {public val context: CoroutineContextpublic fun resumeWith(value: Result<T>)}
context 是 Continuation 将会使用的 CoroutineContext;
resumeWith 会恢复协程的执行,同时传入一个 Result 参数,Result 中会包含导致挂起的计算结果或者是一个异常。
Result
https://github.com/Kotlin/kotlinx.coroutines/blob/master/stdlib-stubs/src/Result.kt
注意: 从 Kotlin 1.3 开始,您也可以使用 resumeWith 对应的扩展函数: resume (value: T) 和 resumeWithException (exception: Throwable)。
resume
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/resume.html
resumeWithException
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/resume-with-exception.html
fun loginUser(userId: String, password: String, completion: Continuation<Any?>) {val user = userRemoteDataSource.logUserIn(userId, password)val userDb = userLocalDataSource.logUserIn(user)completion.resume(userDb)}
使用不同的 Dispatcher
生成状态机
特殊说明: 本文接下来所展示的,并不是与编译器生成的字节码完全相同的代码,而是足够精确的,能够确保您理解其内部发生了什么的 Kotlin 代码。这些声明由版本为 1.3.3 的协程库生成,可能会在其未来的版本中作出修改。
fun loginUser(userId: String, password: String, completion: Continuation<Any?>) {// Label 0 -> 第一次执行val user = userRemoteDataSource.logUserIn(userId, password)// Label 1 -> 从 userRemoteDataSource 恢复val userDb = userLocalDataSource.logUserIn(user)// Label 2 -> 从 userLocalDataSource 恢复completion.resume(userDb)}
fun loginUser(userId: String, password: String, completion: Continuation<Any?>) {when(label) {// Label 0 -> 第一次执行userRemoteDataSource.logUserIn(userId, password)}// Label 1 -> 从 userRemoteDataSource 恢复userLocalDataSource.logUserIn(user)}// Label 2 -> 从 userLocalDataSource 恢复completion.resume(userDb)}else -> throw IllegalStateException(...)}}
接下来,编译器会创建一个私有类,它会:
保存必要的数据;
递归调用 loginUser 函数来恢复执行。
您可以查看下面提供的编译器生成类的近似版本。
特别说明: 注释不是由编译器生成的,而是由作者添加的。添加它们是为了解释这些代码的作用,也能让后面的代码更加容易理解。
fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) {class LoginUserStateMachine(// completion 参数是调用了 loginUser 的函数的回调completion: Continuation<Any?>): CoroutineImpl(completion) {// suspend 的本地变量var user: User? = nullvar userDb: UserDb? = null// 所有 CoroutineImpls 都包含的通用对象var result: Any? = nullvar label: Int = 0// 这个方法再一次调用了 loginUser 来切换// 状态机 (标签会已经处于下一个状态)// result 将会是前一个状态的计算结果override fun invokeSuspend(result: Any?) {this.result = resultloginUser(null, null, this)}}...}
fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) {...val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion)...}
/* Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 */fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) {...val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion)when(continuation.label) {0 -> {// 错误检查throwOnFailure(continuation.result)// 下次 continuation 被调用时, 它应当直接去到状态 1continuation.label = 1// Continuation 对象被传入 logUserIn 函数,从而可以在结束时恢复// 当前状态机的执行userRemoteDataSource.logUserIn(userId!!, password!!, continuation)}1 -> {// 检查错误throwOnFailure(continuation.result)// 获得前一个状态的结果continuation.user = continuation.result as User// 下次这 continuation 被调用时, 它应当直接去到状态 2continuation.label = 2// Continuation 对象被传入 logUserIn 函数,从而可以在结束时恢复// 当前状态机的执行userLocalDataSource.logUserIn(continuation.user, continuation)}... // 故意遗漏了最后一个状态}}
花一些时间浏览上面的代码,看看您是否能注意到与之前代码之间的差异。下面我们来看看编译器生成了什么:
/* Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 */fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) {...val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion)when(continuation.label) {...2 -> {// 错误检查throwOnFailure(continuation.result)// 获取前一个状态的结果continuation.userDb = continuation.result as UserDb// 恢复调用了当前函数的函数的执行continuation.cont.resume(continuation.userDb)}else -> throw IllegalStateException(...)}}
suspend fun loginUser(userId: String, password: String): User {val user = userRemoteDataSource.logUserIn(userId, password)val userDb = userLocalDataSource.logUserIn(user)return userDb}
/* Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 */fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) {class LoginUserStateMachine(// completion 参数是调用了 loginUser 的函数的回调completion: Continuation<Any?>): CoroutineImpl(completion) {// 要在整个挂起函数中存储的对象var user: User? = nullvar userDb: UserDb? = null// 所有 CoroutineImpls 都包含的通用对象var result: Any? = nullvar label: Int = 0// 这个函数再一次调用了 loginUser 来切换// 状态机 (标签会已经处于下一个状态)// result 将会是前一个状态的计算结果override fun invokeSuspend(result: Any?) {this.result = resultloginUser(null, null, this)}}val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion)when(continuation.label) {0 -> {// 错误检查throwOnFailure(continuation.result)// 下次 continuation 被调用时, 它应当直接去到状态 1continuation.label = 1// Continuation 对象被传入 logUserIn 函数,从而可以在结束时恢复// 当前状态机的执行userRemoteDataSource.logUserIn(userId!!, password!!, continuation)}1 -> {// 检查错误throwOnFailure(continuation.result)// 获得前一个状态的结果continuation.user = continuation.result as User// 下次这 continuation 被调用时, 它应当直接去到状态 2continuation.label = 2// Continuation 对象被传入 logUserIn 方法,从而可以在结束时恢复// 当前状态机的执行userLocalDataSource.logUserIn(continuation.user, continuation)}2 -> {// 错误检查throwOnFailure(continuation.result)// 获取前一个状态的结果continuation.userDb = continuation.result as UserDb// 恢复调用了当前函数的执行continuation.cont.resume(continuation.userDb)}else -> throw IllegalStateException(...)}}
推荐阅读