传递viewModelScope创建的SupervisorScope协程作用域时代码崩溃的原因咨询
兄弟,我看了你贴的这段代码,结合你说的崩溃问题,大概能梳理出几个常见的坑,咱们一个个说:
生命周期不匹配引发的取消异常
你在ViewModel里用viewModelScope.launch启动协程,这个协程的生命周期是绑定ViewModel的——ViewModel销毁时,viewModelScope会自动取消所有子协程。如果把这个scope(或者基于它创建的SupervisorScope)传给Usecase,Usecase里用这个scope启动的操作还在运行时ViewModel销毁了,协程就会被取消,抛出CancellationException。要是你没在代码里专门排除这个异常(比如catch里没区分它和普通异常),就会触发崩溃。而且要注意:CancellationException是协程框架用来处理取消逻辑的特殊异常,本来就不该当成错误来捕获处理。SupervisorScope的使用姿势错了
SupervisorScope的核心是“子协程崩溃不影响兄弟协程”,但如果你把ViewModel里创建的SupervisorScope直接传给Usecase,相当于把ViewModel的生命周期控制权交出去了。比如Usecase里用这个scope启动了长期运行的协程,会导致ViewModel无法被正常回收,甚至在ViewModel销毁后,协程还在后台瞎跑,触发各种不可控的崩溃。而且要是Usecase里没正确处理子协程的异常,哪怕是SupervisorScope,也可能把异常抛回给ViewModel的协程,导致崩溃。传递Scope而非Context的风险
很多人会下意识把CoroutineScope传给下层,但正确的姿势应该是传递CoroutineContext。因为Scope包含了生命周期和取消逻辑,下层持有Scope引用很容易导致内存泄漏或者生命周期混乱。比如你传的是ViewModel的子协程Scope,Usecase里要是不小心调用了scope.cancel(),直接就把ViewModel里的协程全干掉了,不崩溃才怪。
给你几个实用的解决方向:
- 让Usecase的方法用suspend修饰,别传Scope
直接把Usecase的方法写成挂起函数,这样它会自动继承调用方(ViewModel)的协程上下文,不需要手动传Scope。示例代码:// ViewModel里的代码 viewModelScope.launch(Dispatchers.IO) { try { tempUsecase.coroutineTest() } catch (e: Exception) { if (e !is CancellationException) { updateScreenState(ScreenState.Error(error = "")) onApiFailure(e) } } } // Usecase里的代码 override suspend fun coroutineTest() { SupervisorScope { launch { /* 执行子任务1 */ } launch { /* 执行子任务2 */ } } } - 在Usecase内部创建SupervisorScope
要是需要隔离子协程的异常,就在Usecase自己的挂起函数里创建SupervisorScope,别从ViewModel传。这样Usecase内部的协程异常不会扩散到ViewModel的协程里。 - 正确处理取消异常
在catch块里排除CancellationException,因为这是协程正常取消的信号,不需要当成错误来处理,避免误触发错误逻辑导致崩溃。
内容来源于stack exchange

