Android中Bitmap压缩策略

1,977 阅读3分钟

一、为什么BItmap需要高效加载

因为现在的高清图片占用内存大,而

Android对单个应用所施加的内存限制,只有十几M,如16M,这导致加载Bitmap的时候很容易出现内存溢出。

如下报错信息:

java.lang.OutOfMemoryError:bitmap size exceeds VM budget

为了解决这个问题,就出现了Bitmap的高效加载策略。

其核心思想很简单。 假设通过ImageView来显示图片,很多时候ImageView并没有原始图片的尺寸那么大,这个时候把整个图片加载进来后再设置给ImageView,显然是没有必要的,因为ImageView根本没办法显示原始图片,这时候可以按一定的采样率来缩小图片再加载进来,这样图片既能在ImageVIew显示出来,也能降低内存占用从而一定程度上避免OOM,提高Bitmap加载时的性能。


二、Bitmap高效加载的具体方式

1、加载Bitmap的方式

BItmap在android中指的是一张图片,通过BitmapFactory类提供的四类方法:

decodeFile、decodeResource、decodeStream和decodeByteArray,分别从文件系统、资源、输入流和字节数组中加载一个Bitmap对象,其中decodeFile和decodeResource又间接调用decodeStream方法,这四类方法最终在Android的底层实现,对应着BitmapFactory类的几个native方法


2、BitmapFatory.Options的参数

  • inSampleSize参数

上述四类方法都支持BitmapFactory.Options参数,而Bitmap是按一定采样率进行缩放,即通过BitmapFactory.Options参数实现,主要用到了inSampleSize参数,即采样率。通过对inSampleSize的设置,对图片的像素的高和宽进行缩放。


当inSampleSize=1,即采样后的图片大小为原始大小,

如inSamoleSize < 1,也按照1来计算

若inSampleSize > 1,即采样后的图片将缩小,缩小比例1/(inSampleSize的二次方)


注意:官方文档指出:inSampleSize的取值应该总是2的指数,如1,2,4,8等,如果外界传入的inSampleSize的孩子不为2的指数,那么系统会向下取整并选择一个最接近2的指数代替。


  • inJustDecodeBounds参数

我们需要获取加载图片的宽高信息,然后交给inSampleSize参数选择缩放比缩放。那么如何能先不加载图片却能获取图片的宽高信息,通过inJustDecodeBounds=true,然后加载图片就可以实现只解析图片的宽高信息,并不会真正的加载图片,所以这个操作时轻量级的,当获取了宽高信息,计算出缩放比后,然后再将inJustDecodeBounds=false,再重新加载图片,就可以加载缩小后的图片。


注意:BitmapFactory获取的图片宽高信息和图片的位置程序运行的设备有关系,比如同一张图片放在不同的drawable目录下或者程序运行在不同屏幕密度的设备上,都可能导致BitmapFactory获取到不同的结果。


3、高效加载Bitmap的流程

  • BitmapFactory.OptionsinJustDecodeBounds参数设置为true并加载图片
  • BitmapFactory.Options中取出图片的原始宽高信息,它们对应的outWidthoutHeight参数
  • 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
  • BitmapFactory.OptionsinJustDecodeBounds参数设为false,然后重新加载图片


三、Bitmap高效加载的代码实现

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, 
              int reqWidth, int reqHeight){
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    //加载图片
    BitmapFactory.decodeResource(res,resId,options);
    //计算缩放比
    options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
    //重新加载图片
    options.inJustDecodeBounds =false;
    return BitmapFactory.decodeResource(res,resId,options);
}

private static int calculateInSampleSize(BitmapFactory.Options options, 
                int reqHeight, int reqWidth) {
    int height = options.outHeight;
    int width = options.outWidth;
    int inSampleSize = 1;
    if(height>reqHeight||width>reqWidth){
        int halfHeight = height/2;
        int halfWidth = width/2;
        //计算缩放比,是2的指数
        while((halfHeight/inSampleSize) >= reqHeight&&
                   (halfWidth/inSampleSize) >= reqWidth){
            inSampleSize*=2;
        }
    }
    return inSampleSize;
}

这个时候可以通过如下方法高效加载图片:

mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100));

除了BItmapFactory的decodeResource方法,其他方法也可以,如decodeFile、decodeByteArray等