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

Android Kotlin API调用:进度条、返回函数与协程问题排查

问题分析与解决方案

核心误区拆解

  1. 重复保存+提前终止的问题
    你用「保存次数」判断任务完成是错误逻辑——协程并发场景下,重复保存(比如同一数据被多协程处理)会让计数提前达标,导致未完成的请求被强行终止。同时Room未设置冲突策略,是重复数据产生的根源。

  2. runBlocking/join导致卡顿的问题
    runBlocking会阻塞当前线程,如果在Android主线程调用,直接触发ANR(应用无响应);join同理,在主线程调用job.join()会卡住UI线程,因为它会等待协程完成期间不释放主线程资源。


针对性解决方案

1. 从根源解决重复保存

首先给Room的DAO插入方法设置冲突策略,避免重复数据写入:

@Dao
interface DataDao {
    // 重复数据直接替换,或用IGNORE忽略重复插入
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertData(data: DataEntity)
}

然后用API请求任务数代替保存次数判断完成状态——跟踪所有需要执行的API请求,而非保存的条目数,确保所有请求都执行完毕再触发跳转。

2. 正确用协程等待所有任务完成

使用CoroutineScope配合async+awaitAll,在IO线程等待所有任务完成,绝不阻塞主线程:

class SplashActivity : AppCompatActivity() {
    private val apiService = Retrofit.create(ApiService::class.java)
    private val dataDao = AppDatabase.getInstance(this).dataDao()
    // 用IO线程+SupervisorJob,单个任务失败不影响其他任务
    private val loadScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        startDataLoading()
    }

    private fun startDataLoading() {
        loadScope.launch {
            // 定义所有需要执行的API请求任务
            val apiTasks = listOf(
                async { fetchAndSave("api/endpoint1") },
                async { fetchAndSave("api/endpoint2") },
                async { fetchAndSave("api/endpoint3") }
            )
            // 等待所有任务完成(不管单个任务成功/失败)
            apiTasks.awaitAll()

            // 所有任务完成后,切回主线程跳转主界面
            withContext(Dispatchers.Main) {
                startActivity(Intent(this@SplashActivity, MainActivity::class.java))
                finish()
            }
        }
    }

    private suspend fun fetchAndSave(endpoint: String) {
        try {
            val response = apiService.getData(endpoint)
            response.body()?.let { dataList ->
                dataList.forEach { data ->
                    dataDao.insertData(data.toEntity())
                }
            }
        } catch (e: Exception) {
            // 单个请求失败不终止全局任务,仅记录日志
            e.printStackTrace()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 页面销毁时取消所有未完成的协程,避免内存泄漏
        loadScope.cancel()
    }
}

3. 额外优化:避免不必要的API请求

结合你的需求(API更新频率低),可以在启动请求前先检查Room缓存:

private suspend fun shouldRequestApi(): Boolean {
    val cachedDataCount = dataDao.getDataCount()
    // 比如缓存为空,或缓存超过7天则重新请求
    return cachedDataCount == 0 || isCacheExpired()
}

// 在startDataLoading()中加入判断
private fun startDataLoading() {
    loadScope.launch {
        if (!shouldRequestApi()) {
            withContext(Dispatchers.Main) {
                jumpToMain()
                return@launch
            }
        }
        // 执行API请求逻辑...
    }
}

关键注意点

  • 永远不要在主线程调用runBlocking或直接join,所有耗时操作必须放在Dispatchers.IO线程
  • 用SupervisorJob确保单个协程失败不会取消其他协程,保证所有请求都能执行
  • 协程作用域要和页面生命周期绑定,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:12:46