Android中Retrofit+协程并行API请求部分未执行问题排查
问题原因分析
- 嵌套协程导致执行不可控:每个API请求方法内部都通过
viewModelScope.launch启动新协程,而外部async等待的是launch返回的Job对象,并非API请求的完成状态。这会导致await()提前结束,内部协程可能因ViewModel生命周期变化被取消,部分请求无法执行完成。 - 方法修饰符错误:
getMatchDetails是普通fun而非suspend fun,async包裹它时会立即执行完毕,不会等待API请求完成,直接进入下一个await(),该请求后续可能被终止。 - 异常处理不规范:
callAll中的catch直接吞掉所有异常,无法排查请求失败原因;同时API方法捕获异常后仅打印日志,未通过LiveData通知UI请求失败,导致UI误以为请求未触发。
解决方案及修正代码
核心修改点
- 移除API方法内部的
viewModelScope.launch,改用withContext(Dispatchers.IO)切换线程,让方法成为真正的挂起函数,确保async能正确等待请求完成。 - 给
getMatchDetails添加suspend修饰符,统一方法签名。 - 完善异常处理:API请求失败时,通过
LiveData发送ResponseState.Error状态;callAll中保留异常日志,避免吞掉关键错误信息。 - 使用
coroutineScope包裹所有并行任务,确保所有请求都能执行完成,即使单个请求失败也不会中断其他任务。
修改后的ViewModel代码
@HiltViewModel class MatchDetailsViewModel @Inject constructor( val api: FootballApi, val app: Application, ) : AndroidViewModel(app) { private val _matchDetailsMutableLiveData = MutableLiveData<ResponseState<FixtureById>>() private val _matchStatsMutableLiveData = MutableLiveData<ResponseState<Stats>>() private val _matchLineupsMutableLiveData = MutableLiveData<ResponseState<Lineups>>() private val _matchBenchMutableLiveData = MutableLiveData<ResponseState<Bench>>() private val _matchSideLinesMutableLiveData = MutableLiveData<ResponseState<SideLine>>() private val _matchStandingsMutableLiveData = MutableLiveData<ResponseState<Standings>>() val matchStandingsLiveData: LiveData<ResponseState<Standings>> = _matchStandingsMutableLiveData val matchSideLinesLiveData: LiveData<ResponseState<SideLine>> = _matchSideLinesMutableLiveData val matchStatsLiveData: LiveData<ResponseState<Stats>> = _matchStatsMutableLiveData val matchDetailsLiveData: LiveData<ResponseState<FixtureById>> = _matchDetailsMutableLiveData val matchLineupsLiveData: LiveData<ResponseState<Lineups>> = _matchLineupsMutableLiveData val matchBenchLiveData: LiveData<ResponseState<Bench>> = _matchBenchMutableLiveData fun callAll() { viewModelScope.launch { try { coroutineScope { val getMatchDetailsCall = async { getMatchDetails(1582601) } val getMatchStatsCall = async { getMatchStats(1582601) } val getMatchLineupsCall = async { getMatchLineups(1582601) } val getMatchBenchCall = async { getMatchBench(1582601) } val getMatchSideLineCall = async { getMatchSideLine(15006543) } val getMatchStandingsCall = async { getMatchStandings(12880) } // 等待所有任务完成 getMatchDetailsCall.await() getMatchStatsCall.await() getMatchLineupsCall.await() getMatchBenchCall.await() getMatchSideLineCall.await() getMatchStandingsCall.await() } } catch (e: Exception) { Log.e("callAll", "并行请求出现异常: ${e.message}") } } } suspend fun getMatchStandings(seasonId: Int) = withContext(Dispatchers.IO) { _matchStandingsMutableLiveData.postValue(ResponseState.Loading()) try { val response = api.getMatchStandings(seasonId = seasonId) Log.i("getMatchStanding()", response.body().toString()) _matchStandingsMutableLiveData.postValue(ResponseState.Success(response.body()!!)) } catch (exception: Exception) { Log.e("getMatchStanding()", exception.toString()) _matchStandingsMutableLiveData.postValue(ResponseState.Error(exception.message ?: "请求失败")) } } suspend fun getMatchSideLine(id: Int) = withContext(Dispatchers.IO) { _matchSideLinesMutableLiveData.postValue(ResponseState.Loading()) try { val response = api.getMatchSideLines(id = id) Log.i("getMatchSideline()", response.body().toString()) _matchSideLinesMutableLiveData.postValue(ResponseState.Success(response.body()!!)) } catch (exception: Exception) { Log.e("getMatchSideline()", exception.toString()) _matchSideLinesMutableLiveData.postValue(ResponseState.Error(exception.message ?: "请求失败")) } } suspend fun getMatchDetails(id: Int) = withContext(Dispatchers.IO) { _matchDetailsMutableLiveData.postValue(ResponseState.Loading()) try { val response = api.getMatchDetails(id = id) Log.i("getMatchDetails()", response.body().toString()) _matchDetailsMutableLiveData.postValue(ResponseState.Success(response.body()!!)) } catch (exception: Exception) { Log.e("getMatchDetails()", exception.toString()) _matchDetailsMutableLiveData.postValue(ResponseState.Error(exception.message ?: "请求失败")) } } suspend fun getMatchStats(id: Int) = withContext(Dispatchers.IO) { _matchStatsMutableLiveData.postValue(ResponseState.Loading()) try { val response = api.getMatchStats(id = id) Log.i("getMatchStats()", response.body().toString()) _matchStatsMutableLiveData.postValue(ResponseState.Success(response.body()!!)) } catch (exception: Exception) { Log.e("getMatchStats()", exception.toString()) _matchStatsMutableLiveData.postValue(ResponseState.Error(exception.message ?: "请求失败")) } } suspend fun getMatchLineups(id: Int = 6) = withContext(Dispatchers.IO) { _matchLineupsMutableLiveData.postValue(ResponseState.Loading()) try { val response = api.getMatchLineups(id = id) Log.i("getMatchLineups()", response.body().toString()) _matchLineupsMutableLiveData.postValue(ResponseState.Success(response.body()!!)) } catch (exception: Exception) { Log.e("getMatchLineups()", exception.toString()) _matchLineupsMutableLiveData.postValue(ResponseState.Error(exception.message ?: "请求失败")) } } suspend fun getMatchBench(id: Int) = withContext(Dispatchers.IO) { _matchBenchMutableLiveData.postValue(ResponseState.Loading()) try { val response = api.getMatchBench(id = id) Log.i("getMatchBench()", response.body().toString()) _matchBenchMutableLiveData.postValue(ResponseState.Success(response.body()!!)) } catch (exception: Exception) { Log.e("getMatchBench()", exception.toString()) _matchBenchMutableLiveData.postValue(ResponseState.Error(exception.message ?: "请求失败")) } } }
修改后的Activity调用代码
// 无需指定Dispatchers.IO,viewModelScope已经处理了线程切换 lifecycleScope.launch { matchDetailsViewModel.callAll() }
额外说明
- 使用
coroutineScope包裹并行任务,确保即使某个任务抛出异常,其他任务仍能执行完成(如果需要某个失败就终止所有任务,可以改用supervisorScope)。 - 所有API方法改为挂起函数后,
async能正确等待请求完成,避免提前结束导致请求被取消。 - 新增的
ResponseState.Error状态可以让UI展示错误提示,提升用户体验。
内容的提问来源于stack exchange,提问作者Ahmed Gamal
相关产品推荐
相关产品推荐

