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
相关产品推荐
相关产品推荐

