子协程的异常传播(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();
相关推荐
alexhilton5 小时前
MVI模式的完整历史、误解和现代Android范式
android·kotlin·android jetpack
pengyu20 小时前
【Kotlin 协程修仙录 · 金丹境 · 后阶】 | 异常防火墙:supervisorScope 与异常隔离的终极奥义
android·kotlin
夏沫琅琊21 小时前
Jetpack Compose + 原生 PDF API 从零搭一个 Android 工具应用 (一)
android·pdf·kotlin
疏狂难除1 天前
JetBrains IDE插件开发教程(五)——File Header
ide·kotlin
Kapaseker2 天前
Android CLI 完全指南
android·kotlin
Coffeeee3 天前
Android功耗优化,哪些代码会让你的机子发热又耗电
android·架构·kotlin
Android打工仔3 天前
Kotlin 编译器是如何把 suspend 函数变成状态机的?
kotlin
Android打工仔3 天前
一个 suspend 函数,为什么会变成一个普通函数?
kotlin
苦瓜花4 天前
【Kotlin】初入门
android·开发语言·kotlin
Kapaseker4 天前
Rust 是如何处理异常的
rust·kotlin