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

