編輯:關於Android編程
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. 代碼
/** * 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左右。做內緩存的時候,有必要考慮下這個問題。