高效使用Bitmaps(一) 大Bitmap的載入
高效使用Bitmaps有什麼好處?
我們常常提到的“Android程式優化”,通常指的是效能和記憶體的優化,即:更快的響應速度,更低的記憶體佔用。Android程式的效能和記憶體問題,大部分都和圖片緊密相關,而圖片的載入在很多情況下很用到Bitmap(點陣圖)這個類。而由於Bitmap自身的特性(將每個畫素的屬性全部儲存在記憶體中),導致稍有不慎就會創建出一個佔用記憶體非常大的Bitmap物件,從而導致載入過慢,還會有記憶體溢位的風險。所以,Android程式要做優化,Bitmap的優化是必不可少的一步。
需要對Bitmap進行優化的情形
首先請看一行程式碼:
mImageView.setImageResource(R.drawable.my_image);
這是一行從資原始檔中載入圖片到ImageView的程式碼。通常這段程式碼沒什麼問題,但有些情況下,你需要對這段程式碼進行優化。例如當圖片的尺寸遠遠大於ImageView的尺寸時,或者當你要在一個ListView或GridView中批量載入一些大小未知的圖片時。實際上,以上這行程式碼會在執行時使用BitmapFactory.decodeStream()方法將資源圖片生成一個Bitmap,然後由這個Bitmap生成一個Drawable,最後再將這個Drawable設定到ImageView。由於在過程中生成了Bitmap,因此如果你使用的圖片過大,就會導致效能和記憶體佔用的問題。另外,需要優化的情形不止這一種,這裡就不再列舉。
下面分步說明使用程式碼來減小Bitmap的尺寸從而達到減小記憶體佔用的方法:
1. 獲取原圖片尺寸
通常,我們使用BitmapFactory.decodeResource()方法來從資原始檔中讀取一張圖片並生成一個Bitmap。但如果使用一個BitmapFactory.Options物件,並把該物件的inJustDecodeBounds屬性設定為true,decodeResource()方法就不會生成Bitmap物件,而僅僅是讀取該圖片的尺寸和型別資訊:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true ;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
2. 根據原圖尺寸和目標區域的尺寸計算出合適的Bitmap尺寸
BitmapFactory.Options類有一個引數inSampleSize,該引數為int型,他的值指示了在解析圖片為Bitmap時在長寬兩個方向上畫素縮小的倍數。inSampleSize的預設值和最小值為1(當小於1時,解碼器將該值當做1來處理),且在大於1時,該值只能為2的冪(當不為2的冪時,解碼器會取與該值最接近的2的冪)。例如,當inSampleSize為2時,一個2000*1000的圖片,將被縮小為1000*500,相應地,它的畫素數和記憶體佔用都被縮小為了原來的1/4:
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 halfHeight = height / 2;
final int halfWidth = width / 2;
// 在保證解析出的bitmap寬高分別大於目標尺寸寬高的前提下,取可能的inSampleSize的最大值
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
3. 根據計算出的inSampleSize生成Bitmap
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 首先設定 inJustDecodeBounds=true 來獲取圖片尺寸
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 計算 inSampleSize 的值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 根據計算出的 inSampleSize 來解碼圖片生成Bitmap
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
這裡有一點要注意,就是要在第二遍decode之前把inJustDecodeBounds設定回false。
4. 呼叫以上的decodeSampledBitmapFromResource方法,使用自定尺寸的Bitmap。
如果你要將一張大圖設定為一個100*100的縮圖,執行以下程式碼:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
到此,使用decodeResource()方法將一個大圖解析為小尺寸bitmap的應用就完成了。同理,還可以使用decodeStream(),decodeFile()等方法做相同的事,原理是一樣的。
延伸:一個Bitmap到底佔用多大記憶體?系統給每個應用程式分配多大記憶體?
· Bitmap佔用的記憶體為:畫素總數 * 每個畫素佔用的記憶體。在Android中,Bitmap有四種畫素型別:ARGB_8888、ARGB_4444、ARGB_565、ALPHA_8,他們每個畫素佔用的位元組數分別為4、2、2、1。因此,一個2000*1000的ARGB_8888型別的Bitmap佔用的記憶體為2000*1000*4=8000000B=8MB。
· Android根據裝置螢幕尺寸和dpi的不同,給系統分配的單應用程式記憶體大小也不同,具體如下表(表格取自Android 4.4 Compatibility Definition Document (CDD)):
螢幕尺寸 | DPI | 應用記憶體 |
small / normal / large | ldpi / mdpi | 16MB |
small / normal / large | tvdpi / hdpi | 32MB |
small / normal / large | xhdpi | 64MB |
small / normal / large | 400dpi | 96MB |
small / normal / large | xxhdpi | 128MB |
xlarge | mdpi | 32MB |
xlarge | tvdpi / hdpi | 64MB |
xlarge | xhdpi | 128MB |
xlarge | 400dpi | 192MB |
xlarge | xxhdpi | 256MB |