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

Android上传高分辨率图片到Firebase出现Canvas超大Bitmap崩溃求解

问题根因

Error Canvas: trying to draw too large(120422400bytes) bitmap.
这个报错是因为你直接加载全分辨率超大图到ImageView,Bitmap内存占用超过了Android系统Canvas的绘制阈值(单Bitmap默认最大占用内存通常限制在100M以内),关闭硬件加速无法解决这个问题,因为核心原因是Bitmap本身尺寸过大。

解决方案

1. 修复预览崩溃问题

你当前用Picasso加载图片时没有限制输出尺寸,超大图会直接加载全量像素导致崩溃,修改onActivityResult中的Picasso加载逻辑,限制预览图尺寸:

@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK
            && data != null && data.getData() != null) {
        mImageUri = data.getData();
        doneImageButtonButton.setVisibility(View.VISIBLE);
        // 新增resize限制预览图尺寸,避免加载超大图崩溃
        Picasso.get()
                .load(mImageUri)
                .resize(1080, 1920) // 按预览需要的最大尺寸设置即可,不需要全分辨率
                .centerInside() // 保持宽高比不裁剪
                .into(uploadImageView);
    }
}

2. 上传前压缩图片到4K分辨率

新增图片压缩工具方法,将超过4K(3840×2160)的图片等比压缩到4K以内,再上传压缩后的图片:
首先添加压缩工具方法:

// 压缩图片到最大4K分辨率,返回压缩后的图片Uri
private Uri compressImageTo4K(Uri originalUri) throws IOException {
    // 1. 读取原图尺寸
    ContentResolver resolver = requireContext().getContentResolver();
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    try (InputStream inputStream = resolver.openInputStream(originalUri)) {
        BitmapFactory.decodeStream(inputStream, null, options);
    }
    int originalWidth = options.outWidth;
    int originalHeight = options.outHeight;
    int maxWidth = 3840;
    int maxHeight = 2160;

    // 2. 计算缩放比例
    float scale = Math.min((float) maxWidth / originalWidth, (float) maxHeight / originalHeight);
    // 原图已经小于4K,不需要压缩
    if (scale >= 1f) {
        return originalUri;
    }
    int targetWidth = Math.round(originalWidth * scale);
    int targetHeight = Math.round(originalHeight * scale);

    // 3. 采样加载压缩后的Bitmap
    options.inJustDecodeBounds = false;
    options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight);
    Bitmap compressedBitmap;
    try (InputStream inputStream = resolver.openInputStream(originalUri)) {
        Bitmap sampledBitmap = BitmapFactory.decodeStream(inputStream, null, options);
        // 精确缩放到目标尺寸
        compressedBitmap = Bitmap.createScaledBitmap(sampledBitmap, targetWidth, targetHeight, true);
        if (sampledBitmap != compressedBitmap) {
            sampledBitmap.recycle();
        }
    }

    // 4. 将压缩后的Bitmap保存到缓存目录,返回Uri
    File cacheDir = requireContext().getCacheDir();
    File compressedFile = new File(cacheDir, "compressed_" + System.currentTimeMillis() + "." + getFileExtension(originalUri));
    try (FileOutputStream fos = new FileOutputStream(compressedFile)) {
        compressedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); // 质量可自行调整
    }
    compressedBitmap.recycle();
    return Uri.fromFile(compressedFile);
}

// 计算Bitmap采样率
private 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;
}

然后修改uploadToFirebase方法,先压缩再上传:

private void uploadToFirebase(Uri mImageUri) {
    if (mImageUri != null) {
        final ProgressDialog progressDialog = new ProgressDialog(getActivity());
        progressDialog.setTitle("Uploading...");
        progressDialog.show();

        try {
            // 先压缩图片到4K以内
            Uri compressedUri = compressImageTo4K(mImageUri);
            StorageReference reference = storageReference.child(System.currentTimeMillis()
                    + "." + getFileExtension(compressedUri));
            reference.putFile(compressedUri)
                    .addOnSuccessListener(taskSnapshot -> {
                        progressDialog.dismiss();
                        Toast.makeText(getActivity(), "Saved Succesfully", Toast.LENGTH_SHORT).show();
                        reference.getDownloadUrl()
                                .addOnSuccessListener(uri -> {
                                    Upload upload = new Upload(uri.toString());
                                    String uploadId = databaseReference.push().getKey();
                                    databaseReference.child(uploadId).setValue(upload);
                                    // 上传完成后删除缓存的压缩文件
                                    new File(compressedUri.getPath()).delete();
                                });
                    })
                    .addOnProgressListener(snapshot -> {
                        double progress = (100.0 * snapshot.getBytesTransferred() / snapshot
                                .getTotalByteCount());
                        progressDialog.setMessage("Uploaded " + (int) progress + "%");
                    })
                    .addOnFailureListener(e -> {
                        progressDialog.dismiss();
                        Toast.makeText(getActivity(), "Error Ocurred" + e.getMessage(), Toast.LENGTH_SHORT).show();
                        // 失败也删除缓存文件
                        new File(compressedUri.getPath()).delete();
                    });
        } catch (IOException e) {
            progressDialog.dismiss();
            Toast.makeText(getActivity(), "图片压缩失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
}

补充注意事项

  • 确保已经申请了对应的存储权限:Android 13及以上申请READ_MEDIA_IMAGES,Android 12及以下申请READ_EXTERNAL_STORAGE
  • 如果对压缩速度和质量有更高要求,也可以引入第三方图片压缩库替换原生压缩逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.13 01:48:04