如何在 Kotlin 中将异步回调转换为同步返回的挂起函数

通过 suspendcancellablecoroutine 将基于回调的异步 api 安全地封装为协程挂起函数,实现“等待结果后再执行下一行”的同步语义,避免阻塞线程且保持代码简洁可读。

在 Kotlin 协程开发中,常会遇到遗留的回调式异步 API(如 Android 的 SettingsClient.checkLocationSettings),它们无法直接返回结果,也不支持 await 语义。你不能也不应使用忙等待、Thread.sleep 或 CountDownLatch 等方式“强行阻塞”主线程——这不仅违背协程非阻塞的设计哲学,还会导致 ANR 或 UI 冻结。

正确做法是:将回调桥接为挂起函数。核心工具是 suspendCancellableCoroutine,它允许你在挂起当前协程的同时注册回调,并在回调触发时恢复执行。

以下是一个完整、健壮的封装示例:

import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

// 封装 areAllTheSettingsGranted 回调为挂起函数
suspend fun Settings.areAllTheSettingsGrantedSuspend(): Boolean {
    return suspendCancellableCoroutine { cont ->
        this.areAllTheSettingsGranted { isGranted ->
            cont.resume(isGranted as Boolean)
        }
        // 可选:处理取消(例如清理资源)
        cont.invokeOnCancella

tion { // 如需取消逻辑(如取消 pending 请求),在此添加 } } } // 同理封装内部的 checkIfLocationServicesAreEnabled suspend fun LocationSettingsManager.checkIfLocationServicesAreEnabledSuspend(): Boolean { return suspendCancellableCoroutine { cont -> checkIfLocationServicesAreEnabled { result -> cont.resume(result) } } }

使用时,只需在协程作用域中调用即可获得“同步感”:

lifecycleScope.launch {
    try {
        val allGranted = settings.areAllTheSettingsGrantedSuspend()
        if (allGranted) {
            // ✅ 此处 result 已确定,安全使用
            startLocationUpdates()
        } else {
            requestPermissions()
        }
    } catch (e: CancellationException) {
        // 协程被取消(如 Activity 销毁),自动处理
        throw e
    }
}

⚠️ 重要注意事项

  • ✅ 必须在协程作用域(如 lifecycleScope、viewModelScope)中调用,否则会编译失败;
  • ✅ suspendCancellableCoroutine 自动支持协程取消,invokeOnCancellation 可用于释放资源;
  • ❌ 不要滥用 runBlocking 包裹此类调用——它会阻塞线程,仅适用于测试或极少数特殊场景;
  • ? 若原始回调可能被多次调用(如流式事件),需确保只调用 cont.resume() 一次,否则抛出 IllegalStateException;必要时可用 cont.tryResume() 做防护。

总结:Kotlin 协程不提供“强制同步”的魔法开关,但通过 suspendCancellableCoroutine,你能以声明式、无锁、可取消的方式,优雅地将回调驱动的异步逻辑转化为直观的顺序代码流——这才是现代 Android/Kotlin 异步编程的推荐实践。