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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 19:54:02