高效加载BitMap

在Android加载图片时,我们常常使用BitMap,但由于Android对单个应用施加的内存限制,常导致我们加载BitMap时容易出现内存泄漏并导致:java.lang.OutofMemoryError:bitmap size exceedsVM budget

##如何解决?

  • 高效、按需加载图片

开始之前,先对BitMap的加载做一个简单回顾(介绍)

BitMap在Android中指的是一张图片,可以是png等任何常见的图片格式
BitMapFactory为我们提供了4类加载方法

  • decodeFile 从文件系统加载
  • decodeResource 从资源加载
  • decodeStream 从输入流加载
  • decodeByteArray 从字节数组加载
    ##采样率 inSampleSize

    很多时候,ImageView本身加载不完一个图片的原始大小,于是我们加载整张图片就是在浪费资源。优化方案就是通过更改 BitmapFactory.Options.inSampleSize (采样率)对图片进行采样缩放,将缩放后的图片展示在ImageView上。

当inSampleSize为1时,采样后的图片大小为原始图片大小(不变);
当inSampleSize大于1,如 2 时,采样后的图片宽高均为原来的 1/2 。像素数自然变为原来了 1/4,自然其内存占用也为原来的 1/4
####举个栗子
一张1024 * 1024像素的图片,采用ARGB8888格式存储,内存占有率就为1024 * 1024 * 4(4MB),如果将 inSampleSize 调整为2,采样后的图片像素变为 512 * 512,内存变为 512 * 512 * 4(1MB)
发现:(像素、内存占用)缩放比例均变为原来的 1/(inSampleSize^2)
另外

官方指出:inSampleSize的取值应为 2 的指数,如果传的不是 2 的指数,系统将向下选择一个最近的2的指数作值(实验证明:当作建议即可)

###步骤

  1. 将BitmapFactory.Options的 inJustDecodeBounds 参数设置为true并加载图片

  2. 从BitmapFactory.options中取出图片的原始宽高信息,对应 outWidthoutHeight

  3. 根据采样率规则并结合目标View所需大小计算出采样率 inSampleSize

  4. 将BitmapFactory.options 的 inJustDecodeBounds 设置为false,然后重新加载图片

####代码

    public static Bitmap decodeSampleBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        //根据目标尺寸计算采样率
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    private static 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;
    }

//使用
mImageView.setImageBitmap(decodeSampleBitmapFromResource(,R.id.myimage,100,100));

注意:当inJustDecodeBounds为true时,BitmapFactory只会解析图的原始宽/高信息,并不会真的获取图片,所以这个操作很轻量。另外这时候获取到的宽高信息与图片的位置以及程序运行的设备有关,同一张drawable目录下的图片在不同屏幕密度的设备上可能得到不同的结果。这与Android资源加载机制有关,这一点平时开发的时候应该也注意到了。