Android Kotlin API调用:进度条、返回函数与协程问题排查
问题分析与解决方案
核心误区拆解
重复保存+提前终止的问题
你用「保存次数」判断任务完成是错误逻辑——协程并发场景下,重复保存(比如同一数据被多协程处理)会让计数提前达标,导致未完成的请求被强行终止。同时Room未设置冲突策略,是重复数据产生的根源。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
相关产品推荐
相关产品推荐

