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

Android 13及以上图片压缩Bitmap宽高为0报错修复咨询

适配全Android版本的图片压缩修复方案

问题根源

  1. Scoped Storage 权限限制:Android 10+(含Android X)的会话范围文件访问模式下,File 对象仅为逻辑占位路径,无法通过 FileInputStream 读取真实图片数据,导致 BitmapFactory.decodeStream 解码失败,返回的 Bitmap 宽高为0。
  2. EXIF 读取未适配:原代码直接通过文件路径读取EXIF信息,在Scoped Storage下会因权限问题读取失败。
  3. 输入方式单一:仅支持 File 作为输入源,未处理会话范围访问返回的 Content Uri。

修复步骤与核心代码修改

1. 重构解码方法,兼容Content Uri与File

替换原 decodeFile 方法,改为通过 ContentResolver 获取流,同时支持两种输入方式:

private static final int IMAGE_QUALITY = 99;
private static final int MAX_SIZE_PIXELS = 1200;

public static Bitmap decodeImage(Context context, Uri uri, int maxSize) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;

    // 先读取图片尺寸
    try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
        BitmapFactory.decodeStream(inputStream, null, options);
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }

    // 计算合适的采样率
    int scale = calculateInSampleSize(options, maxSize, maxSize);

    // 解码图片
    BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
    decodeOptions.inSampleSize = scale;
    try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
        return BitmapFactory.decodeStream(inputStream, null, decodeOptions);
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

2. 修改压缩主方法,适配Scoped Storage

核心修改点:

  • 接收 Uri 作为输入(会话范围访问返回的是Uri)
  • 统一通过 MediaStore 写入文件,规避权限问题
  • 适配不同Android版本的EXIF读取方式
public static Uri compressImage(Context context, Uri imageUri, String displayName, String mimeType) {
    Bitmap source = decodeImage(context, imageUri, MAX_SIZE_PIXELS);
    if (source == null || source.getWidth() == 0 || source.getHeight() == 0) {
        Log.e(ImageCompress.class.getSimpleName(), "Bitmap解码失败,宽高为0");
        return null;
    }

    // 确定压缩格式
    Bitmap.CompressFormat compressFormat;
    if (mimeType.contains("png")) {
        compressFormat = Bitmap.CompressFormat.PNG;
    } else if (mimeType.contains("webp")) {
        compressFormat = Bitmap.CompressFormat.WEBP;
    } else if (mimeType.contains("gif")) {
        return imageUri; // GIF格式不压缩
    } else {
        compressFormat = Bitmap.CompressFormat.JPEG;
    }

    // 缩放图片
    Bitmap resizedBmp = source;
    if (source.getWidth() > MAX_SIZE_PIXELS || source.getHeight() > MAX_SIZE_PIXELS) {
        resizedBmp = resizeBitmap(source, MAX_SIZE_PIXELS);
    }

    // 写入压缩后的图片到MediaStore
    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
    contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Constants.MULTIMEDIA_CHATROOM_11);

    Uri resultUri = null;
    OutputStream outputStream = null;
    try {
        resultUri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
        if (resultUri != null) {
            outputStream = context.getContentResolver().openOutputStream(resultUri);
            resizedBmp.compress(compressFormat, IMAGE_QUALITY, outputStream);
        }
    } catch (IOException e) {
        e.printStackTrace();
        if (resultUri != null) {
            context.getContentResolver().delete(resultUri, null, null);
        }
        resultUri = null;
    } finally {
        try {
            if (outputStream != null) {
                outputStream.flush();
                outputStream.close();
            }
            source.recycle();
            if (resizedBmp != source) {
                resizedBmp.recycle();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 复制EXIF方向信息
    if (resultUri != null) {
        copyExifData(context, imageUri, resultUri);
    }

    return resultUri;
}

private static void copyExifData(Context context, Uri sourceUri, Uri destUri) {
    try {
        ExifInterface sourceExif;
        // Android 9及以上支持直接从InputStream创建ExifInterface
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            try (InputStream inputStream = context.getContentResolver().openInputStream(sourceUri)) {
                sourceExif = new ExifInterface(inputStream);
            }
        } else {
            // 低版本用缓存目录临时文件中转
            File tempFile = createTempFileFromUri(context, sourceUri);
            if (tempFile == null) return;
            sourceExif = new ExifInterface(tempFile.getAbsolutePath());
            tempFile.delete();
        }

        String orientation = sourceExif.getAttribute(ExifInterface.TAG_ORIENTATION);
        if (orientation == null) return;

        ExifInterface destExif;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            try (InputStream inputStream = context.getContentResolver().openInputStream(destUri)) {
                destExif = new ExifInterface(inputStream);
            }
        } else {
            File tempDestFile = createTempFileFromUri(context, destUri);
            if (tempDestFile == null) return;
            destExif = new ExifInterface(tempDestFile.getAbsolutePath());
            tempDestFile.delete();
        }

        destExif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation);
        destExif.saveAttributes();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static File createTempFileFromUri(Context context, Uri uri) {
    try {
        InputStream inputStream = context.getContentResolver().openInputStream(uri);
        File tempFile = File.createTempFile("temp_exif", null, context.getCacheDir());
        FileOutputStream outputStream = new FileOutputStream(tempFile);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        inputStream.close();
        outputStream.close();
        return tempFile;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

private static Bitmap resizeBitmap(Bitmap source, int maxSizePixels) {
    int targetWidth, targetHeight;
    double aspectRatio;

    if (source.getWidth() > source.getHeight()) {
        targetWidth = maxSizePixels;
        aspectRatio = (double) source.getHeight() / (double) source.getWidth();
        targetHeight = (int) (targetWidth * aspectRatio);
    } else {
        targetHeight = maxSizePixels;
        aspectRatio = (double) source.getWidth() / (double) source.getHeight();
        targetWidth = (int) (targetHeight * aspectRatio);
    }

    return Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
}

3. 调用方式调整

会话范围访问图库获取到 Uri 后,直接传入压缩方法:

// 示例:从相册选择图片后的回调处理
Uri selectedImageUri = data.getData();
String displayName = getFileNameFromUri(context, selectedImageUri);
String mimeType = context.getContentResolver().getType(selectedImageUri);
Uri compressedUri = ImageCompress.compressImage(context, selectedImageUri, displayName, mimeType);

补充获取文件名的工具方法:

public static String getFileNameFromUri(Context context, Uri uri) {
    String fileName = null;
    Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    if (cursor != null && cursor.moveToFirst()) {
        int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
        if (nameIndex != -1) {
            fileName = cursor.getString(nameIndex);
        }
        cursor.close();
    }
    return fileName != null ? fileName : "compressed_image_" + System.currentTimeMillis() + ".jpg";
}

关键适配说明

  • 统一用ContentResolver处理流:彻底解决Scoped Storage下File无法访问真实数据的问题
  • EXIF跨版本适配:Android 9+直接从流读取EXIF,低版本用缓存目录临时文件中转,无需外部存储权限
  • 返回Uri而非File:符合Scoped Storage的访问规范,避免File路径无效的问题
  • 资源安全回收:确保Bitmap和流正确关闭,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 01:27:14