Android上传图片至Firebase遇OOM错误:图片压缩无效求解决方案
解决Firebase大图片上传OOM问题及图片压缩优化
这个问题我之前帮开发者排查过很多次,核心问题就是你直接用MediaStore.Images.Media.getBitmap()加载原图的全尺寸Bitmap,不管图片多大都会把整个像素数据塞进内存,大尺寸图片(比如十几兆的原图)肯定会触发OOM。Glide没起到压缩效果,大概率是你没用到它的采样压缩或者指定尺寸加载的正确姿势,下面给你几个靠谱的解决方案:
方案一:用Glide直接生成压缩字节流上传(最推荐,省内存)
Glide本身就擅长高效加载图片,我们可以让它直接把图片缩放到指定尺寸,再转成字节流用于Firebase上传,全程不用手动处理Bitmap的内存问题:
// 用Glide获取压缩后的图片字节流,直接上传到Firebase Glide.with(this) .asBitmap() .load(uri) .override(1024, 1024) // 设定图片最大尺寸,超过会等比缩放,可根据需求调整 .centerCrop() // 可选裁剪方式,fitCenter是完整显示不裁剪,按需选择 .into(new CustomTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 压缩格式选JPEG,质量设80(范围0-100,越高越清晰但体积越大) resource.compress(Bitmap.CompressFormat.JPEG, 80, baos); byte[] imageBytes = baos.toByteArray(); // 上传到Firebase Storage FirebaseStorage.getInstance().getReference("images/your_image_name.jpg") .putBytes(imageBytes) .addOnSuccessListener(taskSnapshot -> { // 上传成功后的逻辑,比如获取下载链接 taskSnapshot.getStorage().getDownloadUrl().addOnSuccessListener(uri -> { String downloadUrl = uri.toString(); // 处理下载链接 }); }) .addOnFailureListener(e -> { // 上传失败的错误处理 e.printStackTrace(); }); // 记得手动回收Bitmap,释放内存 resource.recycle(); } @Override public void onLoadCleared(@Nullable Drawable placeholder) { // 加载被清除时的清理逻辑 } });
方案二:优化你现有的Uri转Bitmap方法(手动采样压缩)
如果一定要保留自己的Uri转Bitmap逻辑,那必须加上采样压缩,只加载原图的部分像素,避免全尺寸加载:
public byte[] getBytesFromUri(Uri uri, int quality, int maxWidth, int maxHeight) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); Bitmap bitmap = null; try { // 第一步:只读取图片尺寸,不加载完整Bitmap到内存 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; InputStream inputStream = getContentResolver().openInputStream(uri); BitmapFactory.decodeStream(inputStream, null, options); if (inputStream != null) inputStream.close(); // 计算合适的采样率,让图片缩放到不超过设定的maxWidth/maxHeight options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight); options.inJustDecodeBounds = false; // 现在开始加载压缩后的Bitmap // 第二步:加载压缩后的Bitmap inputStream = getContentResolver().openInputStream(uri); bitmap = BitmapFactory.decodeStream(inputStream, null, options); if (inputStream != null) inputStream.close(); // 压缩成字节流 if (bitmap != null) { bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); } } catch (IOException e) { e.printStackTrace(); } finally { // 回收Bitmap,释放内存 if (bitmap != null) { bitmap.recycle(); } } return stream.toByteArray(); } // 计算采样率的工具方法:找到最大的2的倍数,让缩放后的尺寸不超过需求 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; }
额外优化:直接上传InputStream(连Bitmap都不用加载)
如果想进一步降低内存占用,可以让Glide直接返回InputStream,传给Firebase的putStream方法,全程不用把图片加载成Bitmap:
Glide.with(this) .asInputStream() .load(uri) .override(1024, 1024) .into(new CustomTarget<InputStream>() { @Override public void onResourceReady(@NonNull InputStream resource, @Nullable Transition<? super InputStream> transition) { FirebaseStorage.getInstance().getReference("images/your_image_name.jpg") .putStream(resource) .addOnSuccessListener(taskSnapshot -> { // 上传成功逻辑 }) .addOnFailureListener(e -> { // 失败处理 }); } @Override public void onLoadCleared(@Nullable Drawable placeholder) {} });
注意事项
- 不要在主线程处理大图片压缩/上传,建议用
Coroutine(Kotlin)或者AsyncTask(Java)放到后台线程,避免ANR - 压缩质量和尺寸要根据业务需求平衡,比如头像可以缩到512x512,质量70;普通图片1024x1024,质量80就足够了
- 上传完成后记得及时回收Bitmap或者关闭流,避免内存泄漏
内容的提问来源于stack exchange,提问作者45davy




