Android中的缩略图加载的内存优化策略

本文链接    http://blog.csdn.net/xiaodongrush/article/details/29355651

1. Why,为什么要加载缩略图?

    有的时候不需要展示原图,只需展示图片的缩略图,可以节省内存。比如:网易新闻中的图片浏览,左边展示的小狮子图片就是一个缩略图,点击这个图片,才会展示原图。

        

2. How,怎么做呢?

     http://developer.android.com/training/displaying-bitmaps/load-bitmap.html给出了一个方法,可以加载一个图片的缩略图。
     BitmapFactory#decodeFile (String pathName, BitmapFactory.Options opts),opts可以指定inJustDecodeBounds和inSampleSize两个参数。当指定inJustDecodeBounds时候,只解析图片的长度和宽度,不载入图片。当指定inSampleSize的时候,会根据inSampleSize载入一个缩略图。 比如inSampleSize=4,载入的缩略图是原图大小的1/4。
     要设置inSampleSize是多少呢?假设原图是1500x700的,我们给缩略图留出的空间是100x100的。那么inSampleSize=min(1500/100, 700/100)=7。我们可以得到的缩略图是原图的1/7。这里如果你要问15:7的图片怎么显示到1:1的区域内,请去看ImageView的scaleType属性。
     但是事实没有那么完美,虽然设置了inSampleSize=7,但是得到的缩略图却是原图的1/4,原因是inSampleSize只能是2的整数次幂,如果不是的话,向下取得最大的2的整数次幂,7向下寻找2的整数次幂,就是4。
     怎么才能不浪费内存呢?大熊同学找到一个方法:Bitmap#createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter),该方法可以将一个Bitmap生成指定大小的BItmap,该方法可以放大图片也可以缩小图片。

    最终缩略图加载过程:

    1. 使用inJustDecodeBounds,读bitmap的长和宽。
    2. 根据bitmap的长款和目标缩略图的长和宽,计算出inSampleSize的大小。
    3. 使用inSampleSize,载入一个大一点的缩略图A
    4. 使用createScaseBitmap,将缩略图A,生成我们需要的缩略图B。
    5. 回收缩略图A。

3. Notice,需要注意的事情

    createScaseBitmap如果原图和目标缩略图大小一致,那么不会生成一个新的bitmap直接返回bitmap,因此,回收的时候,要判断缩略图A是否就是缩略图B,如果说是的话,不要回收。

4. 代码

  1. /** 
  2.  * http://developer.android.com/training/displaying-bitmaps/load-bitmap.html 
  3.  */  
  4. public class BitmapUtils {  
  5.     private static int calculateInSampleSize(BitmapFactory.Options options,  
  6.             int reqWidth, int reqHeight) {  
  7.         final int height = options.outHeight;  
  8.         final int width = options.outWidth;  
  9.         int inSampleSize = 1;  
  10.         if (height > reqHeight || width > reqWidth) {  
  11.             final int halfHeight = height / 2;  
  12.             final int halfWidth = width / 2;  
  13.             while ((halfHeight / inSampleSize) > reqHeight  
  14.                     && (halfWidth / inSampleSize) > reqWidth) {  
  15.                 inSampleSize *= 2;  
  16.             }  
  17.         }  
  18.         return inSampleSize;  
  19.     }  
  20.   
  21.     // 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响  
  22.     private static Bitmap createScaleBitmap(Bitmap src, int dstWidth,  
  23.             int dstHeight) {  
  24.         Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);  
  25.         if (src != dst) { // 如果没有缩放,那么不回收  
  26.             src.recycle(); // 释放Bitmap的native像素数组  
  27.         }  
  28.         return dst;  
  29.     }  
  30.   
  31.     // 从Resources中加载图片  
  32.     public static Bitmap decodeSampledBitmapFromResource(Resources res,  
  33.             int resId, int reqWidth, int reqHeight) {  
  34.         final BitmapFactory.Options options = new BitmapFactory.Options();  
  35.         options.inJustDecodeBounds = true;  
  36.         BitmapFactory.decodeResource(res, resId, options); // 读取图片长款  
  37.         options.inSampleSize = calculateInSampleSize(options, reqWidth,  
  38.                 reqHeight); // 计算inSampleSize  
  39.         options.inJustDecodeBounds = false;  
  40.         Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 载入一个稍大的缩略图  
  41.         return createScaleBitmap(src, reqWidth, reqHeight); // 进一步得到目标大小的缩略图  
  42.     }  
  43.   
  44.     // 从sd卡上加载图片  
  45.     public static Bitmap decodeSampledBitmapFromFd(String pathName,  
  46.             int reqWidth, int reqHeight) {  
  47.         final BitmapFactory.Options options = new BitmapFactory.Options();  
  48.         options.inJustDecodeBounds = true;  
  49.         BitmapFactory.decodeFile(pathName, options);  
  50.         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  51.         options.inJustDecodeBounds = false;  
  52.         Bitmap src = BitmapFactory.decodeFile(pathName, options);  
  53.         return createScaleBitmap(src, reqWidth, reqHeight);  
  54.     }  
  55. }  
/**
 * http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
 */
public class BitmapUtils {
    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;
    }

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

    // 从Resources中加载图片
    public static Bitmap decodeSampledBitmapFromResource(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); // 计算inSampleSize
        options.inJustDecodeBounds = false;
        Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 载入一个稍大的缩略图
        return createScaleBitmap(src, reqWidth, reqHeight); // 进一步得到目标大小的缩略图
    }

    // 从sd卡上加载图片
    public static Bitmap decodeSampledBitmapFromFd(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);
    }
}

5. 测试代码

    代码下载链接    http://download.csdn.net/detail/u011267546/7485045

    在drawable-xxhdpi目录下,一共有四种大图,每个平均5-6MB,一共22.9MB。如果原图加载很容易就崩溃了。如果使用上面的方法加载的话,只加载了234KB。这里说一下234KB怎么来的,4个imageView,目标尺寸都是75dip*50dip,在我的手机GalaxyNexus上面,转化为px之后是150px*100px=15000个像素,每个像素使用ARGB_8888方式存储,需要4个字节。一张图片需要15000*4=60000字节,一共有4个Bitmap,那么就是60000*4=240000字节=234.375 KB。

    如果是布满一个屏幕,至少需要多少内存呢?我的手机是768*1280像素,ARGB_8888方式存储,每个像素4个字节,那么就是768*1280*4=3840KB=3.75MB。所以,缓存3屏幕图像的话,也就11MB左右。做内缓存的时候,有必要考虑下这个问题。

    

发布了3 篇原创文章 · 获赞 30 · 访问量 11万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章