You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动