You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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误以为请求未触发。
解决方案及修正代码

核心修改点

  1. 移除API方法内部的viewModelScope.launch,改用withContext(Dispatchers.IO)切换线程,让方法成为真正的挂起函数,确保async能正确等待请求完成。
  2. 给getMatchDetails添加suspend修饰符,统一方法签名。
  3. 完善异常处理:API请求失败时,通过LiveData发送ResponseState.Error状态;callAll中保留异常日志,避免吞掉关键错误信息。
  4. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 08:59:14