在 Kotlin 协程开发中,异常处理是保证程序稳定性的关键环节。以下从底层机制到实战场景的全面解析,帮助您彻底掌握协程异常处理:
一、协程异常传播机制深度解析
-
结构化并发原则
异常沿协程作用域的层次结构向上传播,父协程默认会取消所有子协程并传播异常,直到被处理或导致根协程崩溃。 -
Job 层次结构
val scope = CoroutineScope(Job()) scope.launch { launch { // 子协程1 throw RuntimeException("Child failed") } launch { // 子协程2 会被取消 } }
子协程1的异常会触发父协程取消,进而取消子协程2
-
关键传播路径
launch
:立即抛出异常async
:延迟到await()
抛出coroutineScope
:任一子协程失败即整体失败
二、异常处理四大核心策略
1. 全局异常捕获(CoroutineExceptionHandler)
val handler = CoroutineExceptionHandler { _, ex ->
FirebaseCrashlytics.getInstance().recordException(ex)
Log.e("GLOBAL", "Caught $ex")
}
CoroutineScope(SupervisorJob() + handler).launch {
throw NetworkException("API call failed")
}
适用场景:日志记录、崩溃上报等全局处理
2. SupervisorJob 隔离策略
val supervisor = SupervisorJob()
val scope = CoroutineScope(coroutineContext + supervisor)
scope.launch { /* 协程A 失败不影响 B */ }
scope.launch { /* 协程B */ }
关键特性:
- 子协程独立失败
- 需配合 CoroutineExceptionHandler 使用
- Android 的 viewModelScope 默认使用 SupervisorJob
3. 局部异常捕获
scope.launch {
try {
fetchData()
} catch (e: IOException) {
showErrorUI(e)
}
}
// 对于 async
val deferred = scope.async { /* 可能抛异常 */ }
try {
deferred.await()
} catch (e: Exception) {
handleError(e)
}
4. 异常传播控制
scope.launch {
supervisorScope { // 独立监管域
launch { /* 子协程1 */ } // 失败不影响外层
launch { /* 子协程2 */ }
}
}
三、典型崩溃场景分析
场景 1:未捕获的根协程异常
// 错误示例:
GlobalScope.launch {
throw Exception("Boom!") // 导致进程崩溃
}
// 正确方案:
GlobalScope.launch(
CoroutineExceptionHandler { _, e ->
handle(e)
}
) {
// ...
}
场景 2:async 异常处理遗漏
// 错误示例:
val deferred = scope.async { throw Exception() }
deferred.await() // 未捕获导致崩溃
// 正确处理:
try {
deferred.await()
} catch (e: Exception) {
// 处理异常
}
场景 3:错误使用作用域
// 错误示例:
coroutineScope {
launch { throw Exception() } // 导致整个作用域取消
launch { delay(1000) } // 被连带取消
}
// 正确方案:
supervisorScope {
launch { /* ... */ }
}
四、进阶调试技巧
-
堆栈分析
开启-Dkotlinx.coroutines.debug
JVM 参数获取完整协程信息 -
异常传播可视化
使用CoroutineName
标识协程:launch(CoroutineName("MainWorker")) { async(CoroutineName("NetworkRequest")) { throw IOException() }.await() }
崩溃日志将显示:
Exception in thread “DefaultDispatcher-worker-1” java.io.IOException
at CoroutineName(NetworkRequest).invokeSuspend(:…)
at CoroutineName(MainWorker).resumeWith(:…) -
单元测试策略
@Test fun testCoroutineException() = runTest { val handler = CoroutineExceptionHandler { _, _ -> } val scope = CoroutineScope(handler) scope.launch { throw Exception() } advanceTimeBy(1000) // 等待协程执行 // 验证异常处理逻辑 }
五、性能优化建议
-
避免过度使用 SupervisorJob
在需要严格错误隔离的场景使用,普通业务逻辑优先考虑结构化并发 -
异常处理开销
高频协程中避免在热路径(hot path)进行 try-catch -
内存泄漏预防
确保 CoroutineExceptionHandler 不持有 Activity/Fragment 引用
六、架构设计最佳实践
-
分层错误处理
- UI 层:展示友好错误提示
- 领域层:转换异常类型
- 数据层:处理原始异常
-
错误类型标准化
sealed class AppError : Exception() { data class NetworkError(val code: Int) : AppError() data class DatabaseError(val query: String) : AppError() object AuthExpired : AppError() }
-
错误恢复机制
suspend fun loadDataWithRetry() { var retryCount = 0 while (retryCount < MAX_RETRY) { try { return fetchData() } catch (e: NetworkException) { delay(exponentialBackoff(retryCount)) retryCount++ } } throw MaxRetryException() }
通过深入理解协程异常传播机制,结合分层处理策略和架构级设计,可显著提升 Kotlin 协程应用的稳定性。关键要点:明确异常处理边界、合理使用监管机制、统一错误处理范式。