Bitmap转ByteArray触发OutOfMemoryError且需控制大小≤10MB如何解决?
问题根因分析
你的代码存在三个核心问题直接触发OOM:
- 未做采样直接加载原图:Bitmap内存占用计算公式为
宽 * 高 * 色彩位深,一张4000*3000分辨率的ARGB_8888格式Bitmap直接占用48MB内存,加上压缩输出的字节数组、ByteArrayOutputStream扩容产生的临时内存,很容易突破Android单进程内存上限 - 使用PNG 100质量压缩:PNG是无损压缩,100质量下压缩率极低,输出的字节数组大小接近甚至超过原始Bitmap内存,很难满足10MB的上传限制
- 未做内存复用优化:ByteArrayOutputStream的
toByteArray()方法会生成一份新的字节数组拷贝,等于你同时持有输出流缓存和结果字节数组两份内存,进一步拉高内存峰值
优化后实现方案
优化思路是优先直接读取原始文件(小于10MB时跳过Bitmap处理)、超过阈值时先降采样再动态调整压缩质量,既控制内存峰值,也保证输出大小不超过10MB:
private static final int MAX_UPLOAD_SIZE = 10 * 1024 * 1024; // 10MB上限 public byte[] getImageByteArrayFromUri(Context context, String uriString) throws IOException { Uri pictureUri = Uri.parse(uriString); int fileSize = 0; // 优先通过ContentResolver查询准确文件大小 Cursor cursor = context.getContentResolver().query(pictureUri, new String[]{MediaStore.Images.Media.SIZE}, null, null, null); if (cursor != null && cursor.moveToFirst()) { fileSize = cursor.getInt(0); cursor.close(); } // 第一步:原始文件小于10MB直接读流返回,跳过Bitmap处理 if (fileSize > 0 && fileSize <= MAX_UPLOAD_SIZE) { try (InputStream inputStream = context.getContentResolver().openInputStream(pictureUri)) { if (inputStream == null) return null; byte[] buffer = new byte[fileSize]; inputStream.read(buffer); return buffer; } } // 第二步:超过10MB,先采样加载Bitmap控制内存占用 BitmapFactory.Options options = new BitmapFactory.Options(); // 仅读取图片宽高,不加载实际像素到内存 options.inJustDecodeBounds = true; try (InputStream decodeBoundsStream = context.getContentResolver().openInputStream(pictureUri)) { BitmapFactory.decodeStream(decodeBoundsStream, null, options); } // 计算采样率:采样后最大边长不超过2048,可根据需求调整 int inSampleSize = 1; int maxDimension = Math.max(options.outWidth, options.outHeight); while (maxDimension / inSampleSize > 2048) { inSampleSize *= 2; } options.inSampleSize = inSampleSize; options.inJustDecodeBounds = false; // 不需要透明通道的话可以打开下面的配置,Bitmap内存直接减半 // options.inPreferredConfig = Bitmap.Config.RGB_565; // 解码采样后的Bitmap Bitmap sampledBitmap; try (InputStream decodeStream = context.getContentResolver().openInputStream(pictureUri)) { sampledBitmap = BitmapFactory.decodeStream(decodeStream, null, options); } if (sampledBitmap == null) return null; // 第三步:动态调整压缩质量,保证输出大小不超过10MB byte[] result = null; int quality = 90; // 初始压缩质量 try { while (quality >= 10) { // 最低质量不低于10,避免图片过糊 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 支持透明可换成WEBP格式,压缩率比JPEG高30%左右 if (sampledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)) { byte[] tempData = baos.toByteArray(); if (tempData.length <= MAX_UPLOAD_SIZE) { result = tempData; break; } } quality -= 10; // 每次降低10个质量点 } } finally { sampledBitmap.recycle(); // 及时释放Bitmap原生内存 } return result; }
额外优化建议
- 如果你的上传框架支持直接传入InputStream/File作为请求体,完全可以跳过转字节数组的步骤,直接读文件上传,内存占用会降低90%以上
- 图片处理逻辑必须放在子线程执行,避免阻塞主线程触发ANR
内容的提问来源于stack exchange,提问作者Omar Shawky
相关产品推荐
相关产品推荐

