返回页面时ViewModel中协程被取消的原因及解决方法
在ViewModel中编写了如下代码:
viewModelScope.launch { val response = request.invoke(coroutineScope) responseBlock?.invoke(response) }.apply { invokeOnCompletion { Log.e("Cancellation", "2---", it) if (showLoading) { loadingCount-- changeLoadingIfNeeded() } } }
使用Jetpack Navigator时,首次创建页面运行正常,但跳转到其他页面再返回后,协程被取消,报错信息:kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelled}@545aaed。
原因分析
问题出在request.invoke(coroutineScope)这一行:你传入的coroutineScope大概率是页面(如Fragment)的生命周期Scope(比如lifecycleScope)。当通过Jetpack Navigator跳转到其他页面时,原页面的Fragment会进入STOPPED状态,其生命周期Scope会被系统自动取消,导致该Scope下的协程(即request内部执行的逻辑)被终止。而viewModelScope的生命周期与ViewModel绑定,只要ViewModel未被销毁(页面跳转返回时ViewModel通常仍存活),该Scope下的协程会继续运行。返回页面后,之前启动的viewModelScope协程尝试继续执行,但内部依赖的已取消协程会触发JobCancellationException。
解决方案
方案一:替换为ViewModel的Scope(推荐)
将传入request的协程Scope改为ViewModel自身的viewModelScope,确保协程生命周期与ViewModel绑定,不受页面跳转影响:
viewModelScope.launch { // 替换传入的coroutineScope为viewModelScope val response = request.invoke(viewModelScope) responseBlock?.invoke(response) }.apply { invokeOnCompletion { Log.e("Cancellation", "2---", it) if (showLoading) { loadingCount-- changeLoadingIfNeeded() } } }
方案二:基于页面生命周期重新触发请求
如果request必须依赖页面的生命周期逻辑,可在页面(Fragment)的onResume方法中重新触发该请求,确保使用当前活跃的页面生命周期Scope:
- 在ViewModel中暴露触发请求的方法:
fun fetchData(pageScope: CoroutineScope) { viewModelScope.launch { val response = request.invoke(pageScope) responseBlock?.invoke(response) }.apply { invokeOnCompletion { Log.e("Cancellation", "2---", it) if (showLoading) { loadingCount-- changeLoadingIfNeeded() } } } }
- 在Fragment的
onResume中调用:
override fun onResume() { super.onResume() viewModel.fetchData(lifecycleScope) }
内容的提问来源于stack exchange,提问作者aloj

