Skip to content

回调转挂起实战

源:suspendCancellableCoroutine 官方指南

将现有的基于回调的异步 API(如 OkHttp Call, Google Play Services Task)转换为挂起函数,是 Kotlin 协程最常见的实战场景。

黄金法则:suspendCancellableCoroutine

严禁使用 suspendCoroutine 请始终使用 suspendCancellableCoroutine。 后者额外提供了对 取消 (Cancellation) 的支持。如果协程被取消,你必须也取消底层的异步任务,否则会导致资源泄漏(网络连接一直被占用)。

标准模板

kotlin
suspend fun fetchUser(id: String): User = suspendCancellableCoroutine { cont ->
    // 1. 发起异步请求
    val call = api.getUser(id, object : Callback<User> {
        override fun onSuccess(user: User) {
            // 2. 成功恢复 (只会执行一次)
            cont.resume(user)
        }

        override fun onError(e: Exception) {
            // 3. 失败恢复
            cont.resumeWithException(e)
        }
    })

    // 4. 注册取消回调 (关键!)
    cont.invokeOnCancellation {
        call.cancel()
    }
}

实战:Google Play Services Task

常见的 Firebase 或 Google API 都返回 Task<T>。我们可以扩展它:

kotlin
// 扩展函数:任何 Task 都能通过 await() 转挂起
suspend fun <T> Task<T>.await(): T = suspendCancellableCoroutine { cont ->
    // 添加成功监听
    addOnSuccessListener { result ->
        cont.resume(result)
    }
    
    // 添加失败监听
    addOnFailureListener { e ->
        cont.resumeWithException(e)
    }
    
    // 取消处理
    cont.invokeOnCancellation {
        // 注意:Task API 可能不支持取消,视具体 API 而定
    }
}

常见坑点

  1. 多重恢复错误: 回调如果被多次触发(例如 onSuccess 和 onError 同时调用),会导致崩溃。suspendCancellableCoroutine 是线程安全的,但你需要确保逻辑正确。
  2. 忘记处理取消: 如果不调用 invokeOnCancellation,当 UI 销毁导致协程取消时,底层的网络请求依然会在后台跑完,白白浪费电量和流量。