重构千行Callback:Android异步回调无损迁移协程Suspend完全指南

Kotlin协程普及的今天,把老代码里的异步回调改造成挂起函数,不仅是技术债务的偿还,更是对自己精神健康的投资。

本文不聊原理,只给方案。六种实战场景,从Retrofit到蓝牙扫描,从权限请求到第三方SDK,全部可复制直接落地。


一、核心武器:suspendCoroutine 和 suspendCancellableCoroutine

改造的本质只有一句话:

把你的"给我回调"变成"协程等你结果"

Kotlin 复制代码
package com.example.cb

import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

class Api {
    // 改造前: Callback给结果
    fun getData(callback: Callback) {
        // doSomeThing

//        callback.onSuccess("jsonData")
        callback.onFailure(Throwable("error"))
    }

    // 改造后: suspend给结果
    suspend fun getDataSuspend(): String = suspendCoroutine { continuation ->
        getData(object : Callback {
            override fun onSuccess(data: String) {
                continuation.resume(data)
            }

            override fun onFailure(e: Throwable) {
                println("onFailure ")
//                continuation.resumeWithException(e) // 抛出异常
                continuation.resume("ErrorJsonData") // 也可以返回增加的数据格式
            }

            override
            fun onFailure2(errorCode : Int, message : String) {
//                continuation.resumeWithException(Throwable("error")) // 抛出异常
                 continuation.resume("ErrorJsonData") // 也可以返回增加的数据格式
            }
        })
    }
}

interface Callback {
    fun onSuccess(data: String)
    fun onFailure(e: Throwable) // 错误信息
    fun onFailure2(errorCode : Int, message : String) // 另外一种 错误码和错误信息
}

就这? 是的,就这。90%的场景用这个模板就能解决。

复制代码
continuation.resumeWithException(Throwable("error")) 会抛出异常异常,正常的工程代码也应该是使用resume的较多,调用者判断数据的合法行进一步进行合法性判断

剩下10%需要支持取消 ,换成suspendCancellableCoroutine

Kotlin 复制代码
suspend fun getDataSuspend2(): String = suspendCancellableCoroutine { continuation ->
        val task = getData(object : Callback {
            override fun onSuccess(data: String) {
                continuation.resume(data)
            }

            override fun onFailure(e: Throwable) {
                println("onFailure ")
//                continuation.resumeWithException(e) // 抛出异常
                continuation.resume("ErrorJsonData") // 也可以返回增加的数据格式
            }

            override
            fun onFailure2(errorCode : Int, message : String) {
//                continuation.resumeWithException(Throwable("error")) // 抛出异常
                continuation.resume("ErrorJsonData") // 也可以返回增加的数据格式
            }
        })
        continuation.invokeOnCancellation { task.cancel() }
    }

记住这两个函数,本文所有案例都是它们的变体。


二、六大高频场景实战

场景1:Retrofit 无协程适配器(最基础)

老代码:

java 复制代码
public interface UserApi {
    @GET("user/{id}")
    void getUser(@Path("id") String id, Callback<User> callback);
}

// 调用
api.getUser("123", new Callback<User>() {
    @Override
    public void onSuccess(User user) {
        runOnUiThread(() -> showUser(user));
    }
    
    @Override
    public void onError(Throwable t) {
        showError(t.getMessage());
    }
});

改造后:

Kotlin 复制代码
// 扩展函数,不改动原有接口
suspend fun UserApi.getUserSuspend(id: String): User = suspendCoroutine { continuation ->
    getUser(id, object : Callback<User> {
        override fun onSuccess(user: User) = continuation.resume(user)
        override fun onError(t: Throwable) = continuation.resumeWithException(t)
    })
}

// 使用
viewModelScope.launch {
    try {
        val user = api.getUserSuspend("123")
        _user.value = user
    } catch (e: Exception) {
        _error.value = e.message
    }
}

要点:

  • 新增suspend后缀方法,不破坏原有同步调用

  • ✅ 异常用resumeWithException抛出,配合try-catch使用

  • 不要在里面切线程,让调用方决定


场景2:权限请求(ActivityResultLauncher)

老代码:

java 复制代码
public class PermissionHelper {
    private ActivityResultLauncher<String> launcher;
    
    public void requestCamera(Activity activity, PermissionCallback callback) {
        launcher = activity.registerForActivityResult(
            new ActivityResultContracts.RequestPermission(),
            isGranted -> {
                if (isGranted) callback.onGranted();
                else callback.onDenied();
            }
        );
        launcher.launch(Manifest.permission.CAMERA);
    }
}

改造后:

Kotlin 复制代码
class PermissionHelper(private val activity: FragmentActivity) {
    
    // 单权限
    suspend fun awaitCameraPermission(): Boolean = suspendCancellableCoroutine { continuation ->
        val launcher = activity.registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { isGranted ->
            continuation.resume(isGranted)
        }
        
        continuation.invokeOnCancellation {
            // 权限请求无法真正取消,但可以标记状态
        }
        
        launcher.launch(Manifest.permission.CAMERA)
    }
    
    // 多权限
    suspend fun awaitPermissions(vararg permissions: String): Map<String, Boolean> =
        suspendCancellableCoroutine { continuation ->
            val launcher = activity.registerForActivityResult(
                ActivityResultContracts.RequestMultiplePermissions()
            ) { result ->
                continuation.resume(result)
            }
            
            launcher.launch(permissions)
        }
}

// 使用
lifecycleScope.launch {
    val granted = permissionHelper.awaitCameraPermission()
    if (granted) openCamera() else showDenied()
}

要点:

  • suspendCancellableCoroutine 提供取消回调

  • ✅ 返回类型直接用Boolean,语义清晰

  • ❌ 不要在里面切线程,权限请求必须在UI线程


场景3:定位(一次性获取)

老代码:

java 复制代码
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, 
    new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            // 获取到位置
        }
    },
    Looper.getMainLooper()
);

改造后:

Kotlin 复制代码
suspend fun LocationManager.awaitNextLocation(
    provider: String = LocationManager.GPS_PROVIDER
): Location? = suspendCancellableCoroutine { continuation ->
    
    val listener = object : LocationListener {
        override fun onLocationChanged(location: Location) {
            continuation.resume(location)
        }
        
        override fun onProviderDisabled(provider: String) {
            if (continuation.isActive) {
                continuation.resume(null)
            }
        }
        
        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
        override fun onProviderEnabled(provider: String) {}
    }
    
    requestSingleUpdate(provider, listener, Looper.getMainLooper())
    
    // 取消时移除监听
    continuation.invokeOnCancellation {
        removeUpdates(listener)
    }
}

// 带超时版本(调用方控制)
lifecycleScope.launch {
    val location = withTimeoutOrNull(5000L) {
        locationManager.awaitNextLocation()
    }
    location?.let { updateMap(it) } ?: showTimeout()
}

要点:

  • invokeOnCancellation 保证资源释放

  • ✅ 返回Location?,允许失败返回null

  • 不封装超时 ,让调用方用withTimeout


场景4:SharedPreferences 监听(单次变化)

老代码:

java 复制代码
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> {
    if ("theme".equals(key)) {
        boolean darkMode = sharedPreferences.getBoolean(key, false);
        applyTheme(darkMode);
    }
});

改造后:

Kotlin 复制代码
// 等待某个key发生变化(一次)
suspend fun SharedPreferences.awaitChange(key: String): Any? =
    suspendCancellableCoroutine { continuation ->
        val listener = SharedPreferences.OnSharedPreferenceChangeListener { prefs, changedKey ->
            if (key == changedKey) {
                val value = prefs.all[key]
                continuation.resume(value)
            }
        }
        
        registerOnSharedPreferenceChangeListener(listener)
        
        continuation.invokeOnCancellation {
            unregisterOnSharedPreferenceChangeListener(listener)
        }
    }

// 如果需要多次监听,用Flow
fun SharedPreferences.changes(key: String): Flow<Any?> = callbackFlow {
    val listener = SharedPreferences.OnSharedPreferenceChangeListener { prefs, changedKey ->
        if (key == changedKey) {
            trySend(prefs.all[key])
        }
    }
    
    registerOnSharedPreferenceChangeListener(listener)
    
    awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
}

// 使用
lifecycleScope.launch {
    val newValue = prefs.awaitChange("dark_mode")
    applyTheme(newValue as Boolean)
}

// 或流式监听
lifecycleScope.launch {
    prefs.changes("dark_mode")
        .collect { value -> applyTheme(value as Boolean) }
}

要点:

  • ✅ 单次用suspendCoroutine,多次用callbackFlow

  • ✅ 两者都支持取消和资源释放


场景5:第三方SDK初始化

老代码:

java 复制代码
public class SDKManager {
    public static void init(Context context, InitCallback callback) {
        // 异步初始化,可能3-5秒
        new Thread(() -> {
            // 耗时操作
            boolean success = doInit(context);
            if (success) {
                callback.onSuccess();
            } else {
                callback.onFailure(1001, "初始化失败");
            }
        }).start();
    }
}

改造后:

Kotlin 复制代码
object SDKManager {
    private var isInitialized = false
    
    suspend fun awaitInit(context: Context): Result<Unit> = 
        suspendCancellableCoroutine { continuation ->
            
            if (isInitialized) {
                continuation.resume(Result.success(Unit))
                return@suspendCancellableCoroutine
            }
            
            init(context, object : InitCallback {
                override fun onSuccess() {
                    isInitialized = true
                    continuation.resume(Result.success(Unit))
                }
                
                override fun onFailure(code: Int, message: String) {
                    continuation.resume(Result.failure(
                        SDKException(code, message)
                    ))
                }
            })
            
            // 10秒超时兜底
            continuation.invokeOnCancellation {
                // 通知SDK取消初始化(如果有)
                // SDKManager.cancelInit()
            }
        }
}

class SDKException(val code: Int, val message: String) : Exception("SDK初始化失败[$code]: $message")

// 使用
class MyApp : Application() {
    val initScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    
    override fun onCreate() {
        super.onCreate()
        
        initScope.launch {
            SDKManager.awaitInit(this@MyApp)
                .onSuccess { Log.d("App", "SDK初始化成功") }
                .onFailure { Log.e("App", "SDK初始化失败", it) }
        }
    }
}

要点:

  • ✅ 返回Result类型,强制调用方处理成功/失败

  • ✅ 防重复初始化

  • ✅ 提供取消钩子


场景6:动画完成监听

老代码:

java 复制代码
view.animate()
    .alpha(0f)
    .setDuration(300)
    .setListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            view.setVisibility(View.GONE);
        }
    })
    .start();

改造后:

Kotlin 复制代码
suspend fun View.awaitAnimationEnd(
    duration: Long = 300,
    targetAlpha: Float = 0f
): Unit = suspendCancellableCoroutine { continuation ->
    
    val listener = object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            continuation.resume(Unit)
        }
    }
    
    animate()
        .alpha(targetAlpha)
        .setDuration(duration)
        .setListener(listener)
        .start()
    
    continuation.invokeOnCancellation {
        clearAnimation()
        animate().cancel()
    }
}

// 使用
lifecycleScope.launch {
    button.awaitAnimationEnd()
    button.visibility = View.GONE  // 动画结束后隐藏
}

// 更优雅:扩展函数直接返回View自身
suspend fun <T : View> T.awaitAnimationEndAndGone(): T {
    awaitAnimationEnd()
    visibility = View.GONE
    return this
}

// 调用
lifecycleScope.launch {
    button.awaitAnimationEndAndGone()
}

要点:

  • ✅ UI操作直接在主线程,不需要切线程

  • ✅ 取消时停止动画

  • ✅ 链式调用提升体验


三、那些年我们踩过的坑

坑1:在封装里切线程

Kotlin 复制代码
// ❌ 错误示范
suspend fun fetchUser(): User = withContext(Dispatchers.IO) {
    api.getUserSuspend("123")  // 多此一举
}

// ✅ 正确做法
suspend fun fetchUser(): User {
    return api.getUserSuspend("123")  // 让调用方决定线程
}

原则:业务层永不封装线程调度。 底层库(Retrofit、Room)可以封,因为你100%确定它必须在IO线程。


坑2:忘记处理取消

Kotlin 复制代码
// ❌ 内存泄漏风险
suspend fun getLocation(): Location = suspendCoroutine { cont ->
    locationManager.requestSingleUpdate(provider, listener, mainLooper)
    // 没有invokeOnCancellation,协程取消后监听还在
}

// ✅ 正确做法
suspend fun getLocation(): Location = suspendCancellableCoroutine { cont ->
    locationManager.requestSingleUpdate(provider, listener, mainLooper)
    cont.invokeOnCancellation {
        locationManager.removeUpdates(listener)  // 必写!
    }
}

原则: 任何需要主动释放资源的回调,都用suspendCancellableCoroutine + invokeOnCancellation


坑3:把多次回调当成单次处理

Kotlin 复制代码
// ❌ 错误:位置会多次回调,但协程只能resume一次
suspend fun LocationManager.awaitLocation(): Location = suspendCoroutine { cont ->
    requestLocationUpdates(provider, 0, 0f, listener)  // 持续回调
    cont.resume(location)  // 第二次回调时崩溃!
}

// ✅ 正确:用requestSingleUpdate,或者用Flow
suspend fun LocationManager.awaitOneShot(): Location = suspendCoroutine { cont ->
    requestSingleUpdate(provider, listener, looper)  // 只回调一次
}

原则: suspendCoroutine 只能resume一次 ,多次回调的场景请用callbackFlow


四、终极工具类:一行代码搞定任何回调

Kotlin 复制代码
/**
 * 回调转挂起工具类
 * 专治各种Callback Hell
 */
object SuspendAdapter {
    
    // 单回调,无取消
    suspend inline fun <T> await(
        crossinline block: (Continuation<T>) -> Unit
    ): T = suspendCoroutine { cont ->
        block(cont)
    }
    
    // 单回调,支持取消
    suspend inline fun <T> awaitCancellable(
        crossinline block: (CancellableContinuation<T>) -> Unit
    ): T = suspendCancellableCoroutine { cont ->
        block(cont)
    }
    
    // 成功/失败双回调
    suspend inline fun <T> awaitResult(
        crossinline onSubscribe: (onSuccess: (T) -> Unit, onError: (Throwable) -> Unit) -> Unit
    ): T = suspendCoroutine { cont ->
        onSubscribe(
            { cont.resume(it) },
            { cont.resumeWithException(it) }
        )
    }
    
    // 可取消的成功/失败双回调
    suspend inline fun <T> awaitResultCancellable(
        crossinline onSubscribe: (onSuccess: (T) -> Unit, onError: (Throwable) -> Unit) -> Cancellable
    ): T = suspendCancellableCoroutine { cont ->
        val cancellable = onSubscribe(
            { cont.resume(it) },
            { cont.resumeWithException(it) }
        )
        cont.invokeOnCancellation { cancellable.cancel() }
    }
}

// 取消接口定义
interface Cancellable {
    fun cancel()
}

使用示例:

Kotlin 复制代码
// 以前
suspend fun getUser(id: String) = suspendCoroutine<User> { cont ->
    api.getUser(id, object : Callback<User> {
        override fun onSuccess(r: User) = cont.resume(r)
        override fun onError(e: Throwable) = cont.resumeWithException(e)
    })
}

// 现在
suspend fun getUser(id: String) = SuspendAdapter.awaitResult<User> { onSuccess, onError ->
    api.getUser(id, object : Callback<User> {
        override fun onSuccess(r: User) = onSuccess(r)
        override fun onError(e: Throwable) = onError(e)
    })
}
复制代码

五、迁移策略:如何安全改造老项目

策略1:保留旧API,新增suspend扩展

Kotlin 复制代码
// 旧API继续保留
public void getUser(String id, Callback<User> callback)

// 新增suspend扩展
suspend fun UserApi.getUserSuspend(id: String): User = suspendCoroutine { ... }

// 逐步迁移
// 阶段1:老代码继续用callback
// 阶段2:新代码用suspend
// 阶段3:淘汰所有callback调用

策略2:从外层向内层推进

Kotlin 复制代码
✅ 推荐顺序:
ViewModel → 一刀切全改成suspend
    ↓
UseCase/Interactor → 同步修改
    ↓
Repository → 实现suspend接口
    ↓
DataSource → 最后改造底层

不要试图从底层DataSource开始改,你会陷入"改一个炸一片"的困境。

策略3:单元测试保驾护航

Kotlin 复制代码
@Test
fun `test getUserSuspend`() = runTest {
    val api = mockk<UserApi>()
    coEvery { api.getUserSuspend("123") } returns User("test")
    
    val repo = UserRepository(api)
    val user = repo.getUserSuspend("123")
    
    assertEquals("test", user.name)
}

必须runTest,它会自动处理协程调度。


六、总结:改造前后对比

维度 回调时代 协程时代
代码结构 嵌套7层是常态 顺序书写像同步
异常处理 每个回调单独处理 try-catch 全搞定
取消支持 手动维护取消标记 invokeOnCancellation
线程切换 到处写runOnUiThread withContext一行
组合并发 CountDownLatch太痛苦 coroutineScope + async
测试难度 异步测试噩梦 runTest 同步化

最后送大家一句话:

回调是描述"怎么做",挂起函数是描述"做什么"。

从Callback到Suspend,不仅是语法的升级,更是编程思维的跃迁。

附录:快速检索表

你想做什么 用什么函数 关键代码
单次回调,无取消 suspendCoroutine resume(value)
单次回调,需取消 suspendCancellableCoroutine invokeOnCancellation { }
多次回调(流) callbackFlow trySend(value) + awaitClose
超时控制 调用方 withTimeout 不封装
线程切换 调用方 withContext 不封装
相关推荐
我命由我123453 小时前
Android多进程开发 - AIDL 参数方向、AIDL 传递自定义对象、AIDL 传递自定义对象(参数方向)
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
龚礼鹏3 小时前
android 13 Launcher中应用列表数量,大小如何控制的?
android
决胜万里3 小时前
zephyr上实现Android Fence机制
android·嵌入式
程序员陆业聪12 小时前
2025 年客户端技术盘点与 2026 年技术展望
android
xhBruce13 小时前
Android USB 存储 冷启动(开机自动插着 U 盘)场景
android·usb·vold
CheungChunChiu13 小时前
在 Android 14 上使用 scrcpy 的投屏问题与解决方案
android·adb
xhBruce14 小时前
Android屏幕旋转DisplayRotation - Android15
android·dms
alexhilton14 小时前
使用LoRA微调Gemma实现移动端推理
android·kotlin·android jetpack
冬奇Lab14 小时前
Zygote进程孵化与应用启动机制:从fork到SystemServer的完整旅程
android·源码阅读