一、代码结构:
scss
CoroutineScope(SupervisorJob() + handler).launch { <--- 父协程(A)
println("父111")
launch { <--- 子协程(B)
throw Exception() <--- B崩了
}
delay(2000) <--- 这行属于 A 的代码
println("父222")
}
真实执行流程:
- B 抛异常
- 因为是 SupervisorJob,异常不会传给父 A
- 但是 B 自己崩溃 → B 被取消
- 重点: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 打印 ✅
四、面试满分回答
- SupervisorJob 只能阻止异常传播,不能阻止子协程取消导致父协程取消。
- 子协程抛异常 → 子协程自己取消 → 子协程所在的父协程 Job 也会被取消。
- 所以父协程后面的
delay和println不会执行。 - 要让父协程不受影响,必须给子协程单独分配 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 处理,不会影响父协程继续执行。
- IO 相关异常(网络 / 文件)
kotlin
scss
// 网络请求失败
try {
val result = apiRequest()
} catch (e: IOException) {
println("网络连接失败,属于业务异常,程序不崩溃")
}
- 数据解析异常
kotlin
scss
try {
json.decodeFromString<User>(jsonStr)
} catch (e: JsonSyntaxException) {
println("后端数据格式错误,解析失败,非程序崩溃")
}
- 参数非法异常
kotlin
kotlin
fun checkAge(age:Int){
try {
if(age < 0) throw IllegalArgumentException("年龄不能为负数")
}catch (e:IllegalArgumentException){
println("传入参数不合法,业务拦截,不会崩")
}
}
- 权限 / 状态异常
kotlin
scss
try {
if(!hasCameraPermission()) throw SecurityException("无相机权限")
}catch (e:SecurityException){
println("缺少权限,提示用户即可,程序正常运行")
}
- 业务自定义异常(最常用)
kotlin
scss
// 自己定义业务异常
class LoginFailException(msg:String):Exception(msg)
try {
throw LoginFailException("账号密码错误")
}catch (e:LoginFailException){
println("登录业务失败,界面弹窗提示,APP不崩溃")
}
- 数组 / 集合越界(捕获后可控)
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();