解决Android中使用Bitmap加载图片发生OOM内存溢出的问题

这两天在写一个上传头像的功能。上传头像到服务器的同时,需要把头像存储在本地。但程序总是运行一段时间后,就因为OOM崩溃了,最终发现问题出在直接使用Bitmap加载图片时,实际上在加载到内存中的时候占用空间的大小远大于图片本身的大小,就容易产生内存溢出的问题。
这个问题困扰了我两天,最后终于解决了,解决办法是对图片进行压缩。因为其实用户头像显示的大小并不大,没有必要使用一张完全高清的图片。决定写下这篇博客以此记录我填坑的结果。

我使用的是一个自己写的一个工具类ImageUtils,完整代码如下:

public class ImageUtils {

    public static Bitmap getBitmapFromPath(String imgPath, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imgPath, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        //避免出现内存溢出的情况,进行相应的属性设置。
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inDither = true;

        return BitmapFactory.decodeFile(imgPath, options);
    }
    
    public static Bitmap getBitmapFromUri(Context context, Uri uri, int reqWidth, int reqHeight) throws Exception {
        InputStream input = context.getContentResolver().openInputStream(uri);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(input, null, options);

        input = context.getContentResolver().openInputStream(uri);
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        //避免出现内存溢出的情况,进行相应的属性设置。
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inDither = true;

        Bitmap bitmap = BitmapFactory.decodeStream(input, null, options);

        if (input != null) {
            input.close();
        }

        return bitmap;
    }

    public static Bitmap getBitmapFromByte(byte[] imgByte, int reqWidth, int reqHeight) {
        InputStream input = null;
        Bitmap bitmap = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);;
        input = new ByteArrayInputStream(imgByte);
        SoftReference softRef = new SoftReference(BitmapFactory.decodeStream(
                input, null, options));
        bitmap = (Bitmap) softRef.get();
        if (imgByte != null) {
            imgByte = null;
        }

        try {
            if (input != null) {
                input.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    public static int calculateInSampleSize( //参2和3为ImageView期待的图片大小
                                             BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // 图片的实际大小
        final int height = options.outHeight;
        final int width = options.outWidth;
        //默认值
        int inSampleSize = 1;
        //动态计算inSampleSize的值
        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;
    }
}

综合网上的各种教程,经过实际测验后,总结成了这里提供的三种方式来生成需要的Bitmap,分别是将图片的路径、Uri、byte数组转换为Bitmap。在每一个函数内,均使用了BitmapFactory.Options来设置inSampleSize就可以缩小图片。如果该值为2,则缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4(小于等于1不缩放)。
既然有了inSampleSize的概念,我们就要对比实际图片大小和ImageView控件的大小,如果使用内存直接处理实际图片的Bitmap从而得到实际大小的话,就失去了图片尺寸裁剪的意义,因为内存已经被消耗了。因此BitmapFactory.Options提供了inJustDecodeBounds标志位,当它被设置为true后,再使用decode系列方法时,并不会真正的分配内存空间,这样解码出来的Bitmap为null,但是可以计算出原始图片的真实宽高,即options.outWidth和options.outHeight。通过这两个值,就可以知道图片是否过大了。

本博客参考了:http://blog.csdn.net/seu_calvin/article/details/52351062 最后神奇的发现这个博主是和我同一个学校的研究生学长。。

猜你喜欢

转载自blog.csdn.net/qq_40121502/article/details/83628271