Bitmap是Android应用程序引起OOM的罪魁祸首之一,当我们从网络上下载图片的时候无法知道网络图片的准确大小,所以为了节约内存,一般会在服务器上缓存 一个缩略图,提升下载速度。除此之外,我们还可以在本地显示图片前将图片进行压缩,使其完全符合imageview的大小,这样可以最大限度避免内存浪费。


本文基本思路:

 (1)获取ImageView的宽和高。


 (2)使用inJustDecodeBounds获取bitmap的长和宽。


 (3)根据bitmap的长款和ImageView的长和宽,计算出压缩比例inSampleSize的大小。


 (4)使用inSampleSize,加载一个比ImageView稍大一点的缩略图a。


 (5)使用Bitmap.createScaseBitmap再次压缩A,将缩略图A生成需要的缩略图B。



体会:


1、直接使用ImageView.setImageResoure 时,  持有的是 图片资源的 原始大小,没有压缩


2、使用ImageLoader 加载图片时,ImagerLoader 会用使用 options.inSampleSize


3、直接按ImageView的宽高比进行压缩,  能保证压缩后的图片与 ImageView 显示上 相同, 做到 最大程度的压缩bitmap,  但cpu 计时会耗时一点。  体现在界面上 就是 图片出现 的更晚了,也不会晚太多, 


结论:


对于 资讯类app, 图片 非常多, OOM概率很大,  此时 在 cpu 和 内存 之间二选一,   更好的做法是 以 cpu运行时间  换取 内存空间, 带来的价值更大。



1.计算ImageView的宽高

int ImageView.getWidth()
   int ImageView.getHeight()


final ImageView iv = new ImageView(context);
   iv.post(new Runnable() {
       @Override
       public void run() {
         int w = iv.getWidth();
         int h = iv.getHeight();
       }
   })

 

2.通过BitmapFactory计算得到压缩后的bitmap对象。



2.1 BitmapFactory.Options介绍



  BitmapFactory提供了多种方式根据不同的图片源来创建Bitmap对象:



  (1)Bitmap BitmapFactory.decodeFile(String pathName, Options opts): 读取SD卡上的图片。



  (2)Bitmap BitmapFactory.decodeResource(Resources res, int id, Options opts) : 读取网络上的图片。



  (3)Bitmap BitmapFactory.decodeResource(Resources res, int id, Options opts) : 读取资源文件中的图片。



BitmapFactory.Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;
  BitmapFactory.decodeFile(pathName,options);
  int imageHeight = options.outHeight;
  int imageWidth = options.outWidth;



这样就可以在不分配内存的情况下直接得到图片的宽高。



2.2 计算图片压缩比例(采样率)



  通常如果图片是针对某种分辨率设计的,直接decode图片不会有什么问题。但是,如果图片不是特定设计,且比较大的话,容易造成OOM。



  假设,一个ImageView大小为512*384,有一张2048*1536的图片需要显示,那么如果加载整张图片,那就造成了浪费,这时候就需要采用不同的采样率对原图片进行一定的压缩。



  计算inSampleSize的值有两种方法:



  方法一: 


public 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 heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);
        // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
        // 一定都会大于等于目标的宽和高。
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;
  }



  方法二:



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


需要注意的是,inSampleSize只能是2的整数次幂,如果不是的话,向下取得最大的2的整数次幂。因为按照2的次方进行压缩会比较高效和方便。 方法一计算出来的inSampleSize值可能不是2的整数次幂,不如计算出来的值是inSampleSize=7,这时会被decode函数向下取为 inSampleSize=4。所以我们一般采用方法二进行计算。



   此时,将设置了inSampleSize的options传给BitmapFactory.decode函数去获取图片,得到的会是一张比ImageView稍大的图片,不过这个图片要比原图小了。

public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(pathName, options);
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        Bitmap src = BitmapFactory.decodeFile(pathName, options);
        return createScaleBitmap(src, reqWidth, reqHeight);
    }



2.3 得到符合ImageView大小的图片



这是通过Bitmap Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)函数来实现的。



这个函数返回一个按要求进行拉伸或者缩小后的bitmap.


private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight, int inSampleSize) {
    // 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响,我们这里是缩小图片,所以直接设置为false
        Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
        if (src != dst) { // 如果没有缩放,那么不回收
            src.recycle(); // 释放Bitmap的native像素数组
        }
        return dst;
    }