Kotlin+Retrofit:仅在Fragment中调用出现HTTP请求Canceled异常
解决Fragment中接口调用抛出HTTP Canceled与JobCancellationException的问题
问题背景
在Fragment中发起接口调用时,出现HTTP FAILED: java.io.IOException: Canceled错误,ViewModel捕获到以下异常:
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelling}@c05b47
但相同的接口调用逻辑在MainActivity中可正常执行,仅在Fragment中触发异常。
核心原因
该问题本质是协程被生命周期触发取消,进而导致OkHttp中断请求:
- ViewModel的
viewModelScope会绑定其宿主的生命周期。若Fragment使用by viewModels()获取ViewModel,ViewModel的生命周期与Fragment绑定,当Fragment被替换、移除或因屏幕旋转重建时,viewModelScope会自动取消所有运行中的协程。 - OkHttp的suspend请求会响应协程取消信号,直接中断网络请求并抛出
IOException: Canceled。 - Activity的生命周期更长,除非Activity本身被销毁,否则其绑定的ViewModel协程不会被轻易取消,因此请求可正常完成。
解决方案
1. 切换为Activity级别的ViewModel
若需要Fragment销毁后请求仍继续执行,或多个Fragment共享同一ViewModel状态,应将ViewModel与Activity绑定:
// 在Fragment中获取Activity级别的ViewModel val syncViewModel: SyncViewModel by activityViewModels()
此时ViewModel的生命周期与Activity一致,Fragment的销毁不会触发协程取消。
2. 过滤协程取消异常
在ViewModel的异常处理中,区分正常的生命周期取消与业务异常,避免不必要的错误提示:
fun initialSync(syncRequest: SyncRequest) { _uiState.value = SyncUiState.Loading viewModelScope.launch(coroutineDispatcherProvider.IO()) { try { val result = getSyncUseCase.execute(syncRequest) _uiState.value = SyncUiState.Loaded(SyncItemUiState(result)) } catch (error: JobCancellationException) { // 协程因生命周期取消,无需处理 } catch (error: Exception) { _uiState.value = SyncUiState.Error(error.message.toString()) } } }
3. 确保请求在Fragment活跃状态下发起
在触发请求前检查Fragment的活跃状态,避免在Fragment即将销毁时发起请求:
button.setOnClickListener { // 检查Fragment是否已添加到Activity if (!isAdded) return@setOnClickListener val syncRequest = SyncRequest(0, 1,false,false) syncViewModel.initialSync(syncRequest) }
内容的提问来源于stack exchange,提问作者A. Cedano
相关产品推荐
相关产品推荐

