用一个函数把传统回调式 API 转换成挂起函数,让异步代码回归顺序书写;suspendCancellableCoroutine 更进一步支持取消回调
在协程出现之前,异步操作通常用回调接口来处理结果。以 Retrofit 的网络请求为例:
// 传统回调式写法
private fun callbackStyle() {
apiService.getResult("params")
.enqueue(object : Callback<Result> {
override fun onResponse(
call: Call<Result>,
response: Response<Result>
) {
showResult(response.body()!!) // 成功回调
}
override fun onFailure(
call: Call<Result>,
t: Throwable
) {
t.printStackTrace() // 失败回调
}
})
}
这种写法的典型问题:
suspendCoroutine 是一个挂起函数,你在它的 lambda 里面调用传统回调式的函数,就可以把这个回调式的函数转换成挂起函数。
// 用 suspendCoroutine 将回调转成挂起
lifecycleScope.launch {
val result = suspendCoroutine { continuation ->
apiService.getResult("params")
.enqueue(object : Callback<Result> {
override fun onResponse(
call: Call<Result>,
response: Response<Result>
) {
continuation.resume(response.body()!!) // 成功:恢复协程,返回结果
}
override fun onFailure(
call: Call<Result>,
t: Throwable
) {
continuation.resumeWithException(t) // 失败:恢复协程并抛出异常
}
})
}
// result 直接可用,像同步代码一样
showResult(result)
}
continuation 是协程的续体,它有两个关键方法:resume(value) 恢复协程并返回成功结果;resumeWithException(t) 恢复协程并抛出异常。两者都会让挂起的协程从 suspendCoroutine 调用处恢复执行。
直接在 launch 中写 suspendCoroutine 会让调用方代码臃肿。最佳实践是把转换逻辑封装为一个独立的挂起函数:
// 调用方:像同步代码一样干净
lifecycleScope.launch {
try {
val result = callbackToSuspend()
showResult(result)
} catch (e: Exception) {
textView.text = e.message // 统一异常处理
}
}
// 封装:将 Retrofit 回调转为挂起函数
private suspend fun callbackToSuspend() = suspendCoroutine { continuation ->
apiService.getResult("params")
.enqueue(object : Callback<Result> {
override fun onResponse(
call: Call<Result>,
response: Response<Result>
) {
continuation.resume(response.body()!!)
}
override fun onFailure(
call: Call<Result>,
t: Throwable
) {
continuation.resumeWithException(t)
}
})
}
这样封装的好处:
try-catch 统一处理,和同步代码一样自然callbackToSuspend 内部实现这就是协程的核心价值之一:把异步代码写成同步的样子。suspendCoroutine 是连接传统回调世界和协程世界的桥梁。
suspendCoroutine 有一个重要局限:它不响应协程的取消。当外部调用 job.cancel() 时,suspendCoroutine 内部发起的网络请求仍然在进行,不会自动中断。
如果希望协程被取消时也能中断底层的网络请求,应该使用 suspendCancellableCoroutine,它支持取消回调。
// 调用方:支持取消
lifecycleScope.launch {
try {
val result = callbackToSuspend()
showResult(result)
} catch (e: Exception) {
textView.text = e.message
}
}
// 封装:使用 suspendCancellableCoroutine
private suspend fun callbackToSuspend() = suspendCancellableCoroutine { cont ->
val call = apiService.getResult("params")
// 核心:协程取消时,同步取消底层网络请求
cont.invokeOnCancellation {
call.cancel() // 取消 OkHttp 的 Call
}
call.enqueue(object : Callback<Result> {
override fun onResponse(
call: Call<Result>,
response: Response<Result>
) {
cont.resume(response.body()!!)
}
override fun onFailure(
call: Call<Result>,
t: Throwable
) {
cont.resumeWithException(t)
}
})
}
invokeOnCancellationinvokeOnCancellation 注册取消回调invokeOnCancellation 是 suspendCancellableCoroutine 提供的唯一额外能力,也是它和 suspendCoroutine 的唯一区别。它注册的回调会在协程被取消时执行,给你一个清理资源的机会。
continuation.resume() 返回成功结果,continuation.resumeWithException() 抛出异常。协程在调用处挂起,在回调处恢复。invokeOnCancellation 注册取消回调,在协程取消时同步取消底层请求,实现完整的资源管理。suspendCoroutine 把回调变成挂起,suspendCancellableCoroutine 进一步让挂起可以被取消 —— 封装成独立函数,调用方就像写同步代码一样处理异步结果。