子协程的异常传播(CoroutineExceptionHandler ):

一、代码结构:

scss 复制代码
CoroutineScope(SupervisorJob() + handler).launch {  <--- 父协程(A)

    println("父111")

    launch { <--- 子协程(B)
        throw Exception() <--- B崩了
    }

    delay(2000) <--- 这行属于 A 的代码
    println("父222")
}

真实执行流程:

  1. B 抛异常
  2. 因为是 SupervisorJob,异常不会传给父 A
  3. 但是 B 自己崩溃 → B 被取消
  4. 重点:B 取消 → 会导致父协程 A 也被取消!!!

为什么?因为:子协程 隶属于 父协程的 Job 子协程崩溃 → 子协程的 Job 取消 → 父协程的 Job 也会被取消即使你外层是 SupervisorJob!

二、真正的原因

🔥 规则 1:

SupervisorJob 只阻止:异常从子 → 父 的传播 但不阻止:子协程崩溃 → 子协程自己取消 → 导致父协程取消

🔥 规则 2:

一个协程体内的所有子协程,共享同一个 Job 子协程崩溃 → Job 取消 → 整个父体所有后续挂起函数全部中断

所以:

plaintext

scss 复制代码
delay(2000)
println("父222")

直接不执行!

3、怎么让父协程不被取消?(正确写法)

你必须给 每个子协程独立分配 SupervisorJob ()

kotlin

scss 复制代码
CoroutineScope(SupervisorJob() + exceptionHandler).launch {
    println("父111")

    // 给子协程单独加 SupervisorJob()
    launch(SupervisorJob()) {
        throw RuntimeException("出错啦")
    }

    delay(2000)
    println("父222") // ✅ 会打印!
}

这样:

  • 子协程崩
  • 子协程自己取消
  • 父协程完全不受影响
  • delay 执行
  • 父 222 打印 ✅

四、面试满分回答

  1. SupervisorJob 只能阻止异常传播,不能阻止子协程取消导致父协程取消。
  2. 子协程抛异常 → 子协程自己取消 → 子协程所在的父协程 Job 也会被取消。
  3. 所以父协程后面的 delayprintln 不会执行。
  4. 要让父协程不受影响,必须给子协程单独分配 SupervisorJob ()

五、最精简记忆口诀

子协程崩 → 子取消 → 父跟着取消(即使是 SupervisorJob)想让父不取消 → 子必须自己带 SupervisorJob ()

六、协程里分 2 种异常

1. 崩溃异常(会取消协程)

  • 直接 throw
  • 空指针、除 0、类型转换错误
  • 结果:协程挂了、Job 取消、父协程受影响

2. 业务异常(不会崩溃、不会取消协程)

  • 只是业务失败
  • 网络失败、解析失败、参数错误、数据库错误
  • try-catch 自己处理
  • 结果:协程继续跑,完全不影响父协程

3.子协程出异常,但不崩溃、不取消、不影响父协程

scss 复制代码
CoroutineScope(SupervisorJob()).launch {
    println("父协程 111")

    // 子协程
    launch {
        try {
            // 这里是业务异常,不是崩溃!
            // 比如网络请求失败、参数错误、解析错误
            throw IOException("网络请求失败!") // 普通业务异常
        } catch (e: Exception) {
            println("子协程自己处理异常:${e.message}")
        }
    }

    delay(2000)
    println("父协程 222") // ✅ 一定会打印!
}
运行结果

plaintext

复制代码
父协程 111
子协程自己处理异常:网络请求失败!
(等待2秒)
父协程 222

4.除了崩溃,还有什么异常?

你直接背这句:

业务异常,比如 IOException(网络失败)、ParseException(解析失败)、IllegalArgumentException(参数错误)、SQLException(数据库错误)等。

这些异常不会导致协程取消,只需要在子协程内部 try-catch 处理,不会影响父协程继续执行。

  1. IO 相关异常(网络 / 文件)

kotlin

scss 复制代码
// 网络请求失败
try {
    val result = apiRequest()
} catch (e: IOException) {
    println("网络连接失败,属于业务异常,程序不崩溃")
}
  1. 数据解析异常

kotlin

scss 复制代码
try {
    json.decodeFromString<User>(jsonStr)
} catch (e: JsonSyntaxException) {
    println("后端数据格式错误,解析失败,非程序崩溃")
}
  1. 参数非法异常

kotlin

kotlin 复制代码
fun checkAge(age:Int){
    try {
        if(age < 0) throw IllegalArgumentException("年龄不能为负数")
    }catch (e:IllegalArgumentException){
        println("传入参数不合法,业务拦截,不会崩")
    }
}
  1. 权限 / 状态异常

kotlin

scss 复制代码
try {
    if(!hasCameraPermission()) throw SecurityException("无相机权限")
}catch (e:SecurityException){
    println("缺少权限,提示用户即可,程序正常运行")
}
  1. 业务自定义异常(最常用)

kotlin

scss 复制代码
// 自己定义业务异常
class LoginFailException(msg:String):Exception(msg)

try {
    throw LoginFailException("账号密码错误")
}catch (e:LoginFailException){
    println("登录业务失败,界面弹窗提示,APP不崩溃")
}
  1. 数组 / 集合越界(捕获后可控)

kotlin

scss 复制代码
val list = listOf(1,2,3)
try {
    val num = list[5]
}catch (e:IndexOutOfBoundsException){
    println("下标越界,做兜底处理,流程继续")
}

5. 最关键区别(面试必问)

1. 崩溃异常

throw NullPointerException()协程取消 → 父协程被影响 → 不打印 222

2. 业务异常

try-catch 包住→ 协程不取消 → 父协程完全正常 → 打印 222

6. 子协程崩溃,防止父协程一起崩溃的方法?

  • 1、 用 try-catch包住子协程崩溃代码;
  • 2、 子协程和父协程用2个SupervisorJob();
相关推荐
alexhilton11 小时前
Android上的ZeroMQ:用发布/订阅模式连接Linux服务
android·kotlin·android jetpack
Fate_I_C1 天前
View Binding的基础使用
android·kotlin·viewbinding
zhangphil1 天前
Android Coil 3 extend ImageRequest‘s custom method/function,Kotlin
android·kotlin
Empty-Filled1 天前
Prompt改版后怎么回归:一套测试集和评分方法
回归·kotlin·prompt
阿巴斯甜2 天前
launch 和 async 内部都是串行,为什么还能实现并发?
kotlin
古怪今人2 天前
Gradle构建工具 Groovy/Kotlin DSL的现代化自动化构建工具
开发语言·kotlin·自动化
赏金术士2 天前
Kotlin 协程与挂起函数(Coroutines & suspend)入门到实战
android·开发语言·kotlin
赏金术士2 天前
Room + Flow 完整教程(现代 Android 官方方案)
android·kotlin·room·compose
赏金术士2 天前
Kotlin 协程面试题大全(Android 高频版)
android·开发语言·kotlin