Android大圖載入記憶體優化(如何防止OutOfMemory)
一、簡介
移動裝置不斷髮展的今天,有的人認為記憶體已經足夠大了,不用再管什麼記憶體優化,Java是虛擬機器可以幫我維護記憶體。其實記憶體空間資源還是很寶貴的,不管手機記憶體有多大,系統分配給單個應用的記憶體空間還是很有限的大致有16M,64M,128M等。在Android中載入大圖會非常消耗系統資源,16M的圖片大致可以儲存3張1024X1536質量為ARGB_8888的圖片,這裡邊還不包含其它Object所佔的資源。軟體在系統上執行,環境是很複雜的,可能測試的時候有限的測試次數上沒有發現記憶體洩漏問題,但是在成千上萬的使用者使用過程中,總會有各種記憶體洩露問題。
二、圖片所佔記憶體空間的計算規則
大圖載入首先要清楚圖片的質量與所佔記憶體的計算規則:Bitmap.Config
Bitmap.Config |
介紹(每個畫素點的構成) |
1pix所佔空間 1byte = 8位 |
1024*1024圖片大小 (解析度) |
ALPHA_8 |
只有透明度,沒有顏色,那麼一個畫素點佔8位。 |
1byte |
1M |
RGB_565 |
即R=5,G=6,B=5,沒有透明度,那麼一個畫素點佔5+6+5=16位 |
2byte |
2M |
ARGB_8888 |
由4個8位組成,即A=8,R=8,G=8,B=8,那麼一個畫素點佔8+8+8+8=32位 |
4byte |
4M |
ARG |
由4個4位組成,即A=4,R=4,G=4,B=4,那麼一個畫素點佔4+4+4+4=16位 |
2byte |
2M |
三、載入和顯示的常見策略
1.直接載入並顯示:
直接把圖片解碼並顯示載入到控制元件上,常用的setImageResource方法就是直接載入圖片到ImageView上邊展示,這種方法是載入原圖的方式;
2.載入到記憶體做縮放:
解碼到記憶體或者Bitmap,判斷Bitmap的大小判斷是否需要二次處理,縮放到指定的大小再顯示;
3.降低質量載入:
4.預載入的方式:
就是先獲取將要載入的圖片的大小,預計算記憶體的佔用和是否我們需要這樣解析度的圖片。
四、大圖的載入優化
1.使用decodeResource方法載入Drawable下邊的圖片
圖片解析度為:8176 x 2368 顏色模型是:ARGB_8888, 完全載入到記憶體所需資源是:73.6M左右,意味著巨大多數的Android手機都會瞬間記憶體洩漏。
載入圖片時先獲取圖片的大小和圖片的格式:將inJustDecodeBounds設定為true,不解碼圖片到記憶體,只讀取圖片的基本資訊。
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;
這時候可以獲取到圖片的大小和格式,然後根據需要載入的模型計算所佔記憶體空間的大小:
/**
* A helper function to return the byte usage per pixel of a bitmap based on its configuration.
*/
public static int getBytesPerPixel(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
} else if (config == Bitmap.Config.RGB_565) {
return 2;
} else if (config == Bitmap.Config.ARGB_4444) {
return 2;
} else if (config == Bitmap.Config.ALPHA_8) {
return 1;
}
return 1;
}
根據圖片的寬和高獲取圖片大小:
/**
* get the image size in the RAM
*
* @param imageW
* @param imageH
* @return
*/
public static long getBitmapSizeInMemory(int imageW, int imageH) {
return imageH * imageW * getBytesPerPixel(Bitmap.Config.ARGB_8888);
}
這時候可以增加一些策略,比如只加載控制元件大小的圖片,或者判斷一下當前的記憶體使用情況,在視情況載入圖片:這時候要把inJustDecodeBounds設定為false,這裡我們演示一下按照指定解析度載入圖片的方式:
/**
* Load bitmap from resources
*
* @param res resource
* @param drawableId resource image id
* @param imgH destination image height
* @param imgW destination image width
* @return
*/
public static Bitmap loadHugeBitmapFromDrawable(Resources resources, int drawableId, int imgH, int imgW) {
Log.d(TAG, "imgH:" + imgH + " imgW:" + imgW);
BitmapFactory.Options options = new BitmapFactory.Options();
//preload set inJustDecodeBounds true, this will load bitmap into memory
options.inJustDecodeBounds = true;
//options.inPreferredConfig = Bitmap.Config.ARGB_8888;//default is Bitmap.Config.ARGB_8888
BitmapFactory.decodeResource(resources, drawableId, options);
//get the image information include: height and width
int height = options.outHeight;
int width = options.outWidth;
String mimeType = options.outMimeType;
Log.d(TAG, "width:" + width + " height:" + height + " mimeType:" + mimeType);
//get sample size
int sampleSize = getScaleInSampleSize(width, height, imgW, imgH);
options.inSampleSize = sampleSize;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Log.d(TAG, "memory size:" + getBitmapSizeInMemory(width / sampleSize, height / sampleSize));
Bitmap bitmap = BitmapFactory.decodeResource(resources, drawableId, options);
Log.d(TAG, "w=" + bitmap.getWidth() + " h=" + bitmap.getHeight() + " bitmap size:" + bitmap.getRowBytes() * bitmap.getHeight());
return bitmap;
}
2.使用decodeFile方法載入SDCard下邊的圖片
圖片資訊同上。/**
* load the bitmap from SDCard with the imgW and imgH
*
* @param imgPath resource path
* @param imgH result image height
* @param imgW result image width
* @return result bitmap
*/
public static Bitmap loadHugeBitmapFromSDCard(String imgPath, int imgH, int imgW) {
Log.d(TAG, "imgH:" + imgH + " imgW:" + imgW);
BitmapFactory.Options options = new BitmapFactory.Options();
//preload set inJustDecodeBounds true, this will load bitmap into memory
options.inJustDecodeBounds = true;
//options.inPreferredConfig = Bitmap.Config.ARGB_8888;//default is Bitmap.Config.ARGB_8888
BitmapFactory.decodeFile(imgPath, options);
//get the image information include: height and width
int height = options.outHeight;
int width = options.outWidth;
String mimeType = options.outMimeType;
Log.d(TAG, "width:" + width + " height:" + height + " mimeType:" + mimeType);
//get sample size
int sampleSize = getScaleInSampleSize(width, height, imgW, imgH);
options.inSampleSize = sampleSize;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Log.d(TAG, "memory size:" + getBitmapSizeInMemory(width / sampleSize, height / sampleSize));
Bitmap bitmap = BitmapFactory.decodeFile(imgPath, options);
Log.d(TAG, "w=" + bitmap.getWidth() + " h=" + bitmap.getHeight() + " bitmap size:" + bitmap.getRowBytes() * bitmap.getHeight());
return bitmap;
}
兩種方式的原理完全一樣。
根據官方文件我們可以瞭解到,inSampleSize必須是2的指數倍,如果不是2的指數倍會自動轉位教下的2的指數倍的數,下邊我提供一個計算方法:
/**
* get the scale sample size
*
* @param resW resource width
* @param resH resource height
* @param desW result width
* @param desH result height
* @return
*/
public static int getScaleInSampleSize(int resW, int resH, int desW, int desH) {
int scaleW = resW / desW;
int scaleH = resH / desH;
int largeScale = scaleH > scaleW ? scaleH : scaleW;
int sampleSize = 1;
while (sampleSize < largeScale) {
sampleSize *= 2;
}
Log.d(TAG, "sampleSize:" + sampleSize);
return sampleSize;
}
接下來,我講繼續講解:圖片載入之圖片快取和非同步載入技術。