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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章