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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 18:36:07