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

Android Kotlin协程请求API返回结果顺序错乱问题求助

问题分析

你的代码存在两个核心问题导致结果乱序/重复:

  1. 共享成员变量id在多协程中被并发修改,存在竞态条件——多个协程可能同时读取到相同的id值,导致请求重复;
  2. 每次调用fetchApiResponse都会启动独立协程,API响应时间不确定,先完成的请求会先回调View,导致结果顺序随机。
解决方案

方案一:串行执行请求(严格按顺序发起+返回)

适合需要严格按顺序发起请求的场景(比如API有调用频率限制),前一个请求完成后再发起下一个,保证结果顺序完全符合预期。

修改代码如下:

object RestModel : MVPContract.Model {

    private val apiClient = APIClient.getInstance()
    private var job: Job? = null

    // 新增方法:一次性处理1到10的串行请求
    fun fetchSequentialResponses(presenter: MVPContract.Presenter) {
        job = CoroutineScope(Dispatchers.IO).launch {
            for (id in 1..10) {
                try {
                    val response = apiClient.getCharacterById(id)
                    withContext(Dispatchers.Main) {
                        val apiResponse = presenter.processRawJson(response.body()!!)
                        presenter.passResponseToView(apiResponse)
                    }
                    // 可选:如果API有频率限制,添加间隔
                    // delay(300)
                } catch (ex: Exception) {
                    withContext(Dispatchers.Main) {
                        presenter.passErrorResponseMessageToView(ex.message.toString())
                    }
                }
            }
        }
    }

    fun cancelJob() {
        job?.cancel()
    }

    // 保留原接口的话,改为接收id参数,避免共享变量
    override fun fetchApiResponse(id: Int, presenter: MVPContract.Presenter) {
        job = CoroutineScope(Dispatchers.IO).launch {
            try {
                val response = apiClient.getCharacterById(id)
                withContext(Dispatchers.Main) {
                    val apiResponse = presenter.processRawJson(response.body()!!)
                    presenter.passResponseToView(apiResponse)
                }
            } catch (ex: Exception) {
                withContext(Dispatchers.Main) {
                    presenter.passErrorResponseMessageToView(ex.message.toString())
                }
            }
        }
    }
}

使用示例(Presenter中):

// 方式1:调用新增的串行方法
RestModel.fetchSequentialResponses(this)

// 方式2:外部循环传入id(需确保串行执行)
CoroutineScope(Dispatchers.Main).launch {
    for (id in 1..10) {
        // 用async/await保证串行
        async(Dispatchers.IO) {
            RestModel.fetchApiResponse(id, this@MyPresenter)
        }.await()
    }
}

方案二:并行请求+按顺序整理结果

适合API支持并发请求的场景,同时发起所有请求,等全部完成后按id排序再返回,效率更高。

修改代码如下:

object RestModel : MVPContract.Model {

    private val apiClient = APIClient.getInstance()
    private var job: Job? = null

    fun fetchParallelOrderedResponses(presenter: MVPContract.Presenter) {
        job = CoroutineScope(Dispatchers.IO).launch {
            // 批量创建并行请求的Deferred任务
            val deferredTasks = (1..10).map { id ->
                async {
                    // 保存id和对应响应,用于后续排序
                    id to apiClient.getCharacterById(id)
                }
            }
            // 等待所有请求完成
            val allResults = deferredTasks.awaitAll()
            // 按id排序结果
            val sortedResults = allResults.sortedBy { it.first }
            
            // 切换到主线程,按顺序回调View
            withContext(Dispatchers.Main) {
                sortedResults.forEach { (id, response) ->
                    try {
                        val apiResponse = presenter.processRawJson(response.body()!!)
                        presenter.passResponseToView(apiResponse)
                    } catch (ex: Exception) {
                        presenter.passErrorResponseMessageToView(ex.message.toString())
                    }
                }
            }
        }
    }

    fun cancelJob() {
        job?.cancel()
    }
}

使用示例:

RestModel.fetchParallelOrderedResponses(this)
关键注意事项
  • 彻底抛弃共享可变的id成员变量,改用参数传递id,避免线程安全问题;
  • 协程作用域建议使用Android生命周期绑定的viewModelScope/lifecycleScope,而非直接创建CoroutineScope(Dispatchers.IO),防止内存泄漏;
  • 每个请求单独处理异常,避免单个请求失败导致整个流程中断。

内容的提问来源于stack exchange,提问作者user21597761

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 10:45:17