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 |
不封装 |