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

CoroutineWorker中执行Bitmap操作时出现OOM问题求助

Worker中异步处理大量Bitmap导致OOM的协程解决方案

问题背景

我需要处理约2000个文件,其中部分需解码Bitmap,该操作占用大量内存。当前实现代码如下:

override suspend fun doWork(): Result {
     (0..2000).forEach { i ->
         analyze(uris[i])
       }
     return Result.success()
}

fun analyze(uri) {
   val bitmap = decodeBitmap(uri)
 .onSuccess{
      applyColorFilter()
  }.onSuccess {
   saveBitmapToFileSystem() 
  }
 }

运行10秒后出现OOM,原因是解码、颜色滤镜处理及文件保存均为异步操作,导致大量Bitmap堆积在内存中(例如已解码100个Bitmap,但仅10个完成滤镜处理),最终引发内存溢出。

请问是否有办法在Worker中执行异步操作且避免该问题?我曾尝试将流程改为递归,但似乎无法在Worker中运行递归,是否有合适的协程解决方案?


解决方案

核心问题

一次性启动2000个异步任务,所有Bitmap同时驻留内存等待后续处理,直接触发内存溢出。解决关键是控制并发数,让内存中同时存在的Bitmap数量保持在安全范围。

方案1:串行处理(内存占用最低)

将analyze改为挂起函数,确保一个文件处理完成后再启动下一个任务,彻底避免多Bitmap堆积:

override suspend fun doWork(): Result {
    // 协程环境下的forEach默认串行执行(除非指定Dispatcher)
    uris.forEach { uri ->
        analyze(uri)
    }
    return Result.success()
}

// 改为挂起函数,确保所有异步步骤完成后再返回
suspend fun analyze(uri: Uri) {
    decodeBitmap(uri)
        .onSuccess { bitmap ->
            applyColorFilter(bitmap)
        }
        .onSuccess { filteredBitmap ->
            saveBitmapToFileSystem(filteredBitmap)
            // 主动回收Bitmap,加速内存释放
            filteredBitmap.recycle()
        }
        .onFailure {
            // 处理解码/滤镜/保存失败的异常
        }
}

// 把原异步解码逻辑包装成挂起函数(如果是回调式异步)
suspend fun decodeBitmap(uri: Uri): Result<Bitmap> = suspendCoroutine { cont ->
    val options = BitmapFactory.Options().apply {
        // 可选:用RGB_565减少内存占用
        inPreferredConfig = Bitmap.Config.RGB_565
        // 可选:采样缩小尺寸
        inSampleSize = 2
    }
    try {
        val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri), null, options)
        cont.resume(Result.success(bitmap!!))
    } catch (e: Exception) {
        cont.resume(Result.failure(e))
    }
}

suspend fun applyColorFilter(bitmap: Bitmap): Result<Bitmap> {
    // 颜色滤镜处理逻辑,包装成挂起函数
    return runCatching {
        val paint = Paint().apply {
            colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) })
        }
        val filtered = Bitmap.createBitmap(bitmap.width, bitmap.height, bitmap.config)
        val canvas = Canvas(filtered)
        canvas.drawBitmap(bitmap, 0f, 0f, paint)
        bitmap.recycle() // 回收原Bitmap
        filtered
    }
}

suspend fun saveBitmapToFileSystem(bitmap: Bitmap): Result<Unit> {
    // 保存文件逻辑,包装成挂起函数
    return runCatching {
        val outputStream = FileOutputStream(File(context.filesDir, "filtered_${System.currentTimeMillis()}.png"))
        bitmap.compress(Bitmap.CompressFormat.PNG, 90, outputStream)
        outputStream.close()
        bitmap.recycle() // 最终回收处理后的Bitmap
    }
}

方案2:限制并发数(兼顾速度与内存)

如果串行处理效率太低,可通过Semaphore限制同时运行的任务数(比如5个),在保证内存安全的前提下提升处理速度:

override suspend fun doWork(): Result {
    // 限制最大并发任务数为5
    val semaphore = Semaphore(5)
    coroutineScope {
        uris.forEach { uri ->
            launch {
                semaphore.acquire() // 获取许可,达到上限则等待
                try {
                    analyze(uri)
                } finally {
                    semaphore.release() // 释放许可,让下一个任务可以启动
                }
            }
        }
    }
    return Result.success()
}

// analyze函数同方案1,确保处理完回收Bitmap
suspend fun analyze(uri: Uri) {
    decodeBitmap(uri)
        .onSuccess { bitmap ->
            applyColorFilter(bitmap).onSuccess { filtered ->
                saveBitmapToFileSystem(filtered)
            }
        }
        .onFailure {
            // 异常处理
        }
}

关键优化细节

  • 主动回收Bitmap:每次处理完成后调用bitmap.recycle(),并及时置空引用,帮助GC快速释放内存。
  • 优化Bitmap配置:解码时使用Bitmap.Config.RGB_565(比ARGB_8888节省一半内存),或通过inSampleSize采样缩小Bitmap尺寸。
  • 避免全局协程:在coroutineScope内启动子协程,确保所有子任务完成后doWork才返回,符合Worker的生命周期要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 09:05:38