Android嵌套CoroutineScopes并行上传图片 如何判断全部完成启用下一步按钮
问题根因
你的原有实现无法准确判定全部上传完成的核心原因有3个:
- 你是在遍历图片的过程中累加
totalImagesNeedToUpload总数量,而上传子协程的执行速度可能快于父协程的遍历速度,会出现总数量还没统计完成,部分图片已经上传完成的情况,导致最终相等判断无法命中 totalImagesUploaded属于多协程并发修改的变量,没有加同步保护,会出现计数丢失、数值不准的问题- 你为每个上传任务创建了独立的
CoroutineScope,没有和父协程绑定,无法通过结构化并发的能力统一等待所有任务执行完成
优化方案
我们可以利用协程的结构化并发特性,先收集所有待上传的文件,再统一开启并行上传任务,最后等待所有任务执行完成即可,不需要手动维护计数变量,逻辑更简洁也更可靠:
// 首先在类级别定义协程Scope,和页面生命周期绑定,避免内存泄漏 // 比如在Activity/Fragment里可以直接用viewModelScope,或者自己定义: private val uploadScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private fun startUploading(){ // 先获取AWS配置 val accessKey = sharedPreferences.getString(getString(R.string.aws_access_key), "").toString() val secretKey = sharedPreferences.getString(getString(R.string.aws_secret_key), "").toString() val bucketName = sharedPreferences.getString(getString(R.string.aws_bucket_name), "").toString() val region = sharedPreferences.getString(getString(R.string.aws_region), "").toString() val distributionUrl = sharedPreferences.getString(getString(R.string.aws_distribution_url), "").toString() uploadScope.launch { // 第一步:先收集所有待上传的文件,提前统计好位置信息,避免边遍历边上传的计数问题 val uploadTasks = mutableListOf<suspend () -> Unit>() allCapturedImages.forEachIndexed { groupIndex, capturedImage -> val imageFiles = capturedImage.viewItem.ImageFiles imageFiles.forEachIndexed { fileIndex, file -> if (!file.uploaded) { uploadTasks.add { // 单个文件上传逻辑 while (true) { val internetActive = utilsClassInstance.hasInternetConnected() if (internetActive) { try { val cfUrl = utilsClassInstance.uploadFile( file.imageFile, accessKey, secretKey, bucketName, region, distributionUrl ) file.uploaded = true file.uploadedUrl = cfUrl // 单张上传完成,切主线程更新UI,隐藏对应进度条 withContext(Dispatchers.Main) { mainRecyclerAdapter?.uploadCompleteForViewItemImage(groupIndex, fileIndex, cfUrl) } break } catch (e: Exception) { e.printStackTrace() // 上传失败加重试延迟,避免频繁占用资源 delay(1000) } } else { // 网络不可用,等待1秒再重试,避免死循环空转 delay(1000) } } } } } } // 第二步:并行执行所有上传任务,awaitAll会等待全部任务执行完成再往下走 uploadTasks.map { async { it.invoke() } }.awaitAll() // 所有任务执行完成,切主线程启用下一步按钮 withContext(Dispatchers.Main) { updateProgressForAllImages() binding.btnNext.isEnabled = true } } } // 页面销毁时取消所有上传任务,避免内存泄漏 override fun onDestroy() { super.onDestroy() uploadScope.cancel() }
核心优化点
- 提前收集所有待上传任务,避免边遍历边上传的计数误差
- 所有上传任务都是同一个父协程的子协程,通过
awaitAll()可以100%准确判定所有任务执行完成,不需要手动维护计数 - 用
SupervisorJob保证单个上传任务失败不会影响其他上传任务 - 网络不可用、上传失败时加了延迟,避免无意义的空转占用系统资源
- 协程Scope和页面生命周期绑定,页面销毁时可以统一取消所有上传任务,避免内存泄漏
内容的提问来源于stack exchange,提问作者Sanket Bhangale
相关产品推荐
相关产品推荐

