1. 程式人生 > >Android中對圖片的記憶體優化方法

Android中對圖片的記憶體優化方法

Android 中對於圖片的記憶體優化方法

1. 對圖片本身進行操作

儘量不要使用 setImageBitmapsetImageResourceBitmapFactory.decodeResource來設定一張大圖,因為這些方法在完成 decode 後,最終都是通過 Java 層的createBitmap來完成的,需要消耗更多記憶體。

因此,改用先通過 BitmapFactory.decodeStream 方法,創建出一個 bitmap,再將其設為 ImageView 的 source,decodeStream 最大的祕密在於其直接呼叫 JNI>>nativeDecodeAsset() 來完成 decode,無需再使用 Java 層的 createBitmap,從而節省了 Java 層的空間。

如果在讀取時加上圖片的 Config 引數,可以更有效的減少載入的記憶體,從而更有效阻止丟擲記憶體異常。另外,decodeStream 直接拿圖片來讀取位元組碼了,不會根據機器的各種解析度來自動適應,使用了 decodeStream 之後,需要在 hdpi 和 mdpi,ldpi 中配置相應的圖片資源, 否則在不同解析度機器上都是同樣大小(畫素點數量),顯示出來的大小就不對了。


程式碼如下:

InputStream is = this.getResources().openRawResource(R.drawable.pic);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 2;
Bitmap btp =BitmapFactory.decodeStream(is,null,options);

以上程式碼即是讀取 drawable 下名為 pic 圖片的縮圖,長度、寬度都只有原圖片的 1/2。圖片大小減少,佔用的記憶體自然也變小了。這麼做的弊端是圖片質量變差,inSampleSize 的值越大,圖片的質量就越差。由於各手機廠商縮放圖片的演算法不同,在不同手機上的縮放圖片質量可能會不同。


2. 呼叫圖片的 recycle() 方法

程式碼如下:

if(!bmp.isRecycle() ){
   bmp.recycle()    //回收圖片所佔的記憶體
   system.gc()      //提醒系統及時回收
}

這種方法其實不是真正降低圖片記憶體的方法。主要目的是標記圖片物件,方便回收圖片物件的本地資料。

圖片物件的本地資料佔用的記憶體最大,而且與程式 Java 部分的記憶體是分開計算的。所以經常出現 Java heap 足夠使用,而圖片發生 OutOfMemoryError 的情況。在圖片不使用時呼叫該方法,可以有效降低圖片本地資料的峰值,從而減少 OutOfMemoryError 的概率。

不過呼叫了 recycle() 的圖片物件處於“廢棄”狀態,呼叫時會造成程式錯誤。所以在無法保證該圖片物件絕對不會被再次呼叫的情況下,不建議使用該方法。特別要注意已經用 setImageBitmap(Bitmapimg) 方法分配給控制元件的圖片物件,可能會被系統類庫呼叫,造成程式錯誤。


3. 以最省記憶體的方式讀取本地資源的圖片

程式碼如下:

/**
 * 以最省記憶體的方式讀取本地資源的圖片
 */
public static Bitmap readBitMap(Context context, int resId){  
   BitmapFactory.Options opt = new BitmapFactory.Options();  
   opt.inPreferredConfig = Bitmap.Config.RGB_565;  
   opt.inPurgeable = true;  
   opt.inInputShareable = true;  
   // 獲取資源圖片  
   InputStream is = context.getResources().openRawResource(resId);  
   return BitmapFactory.decodeStream(is,null,opt);  
}

Android 中載入圖片的顏色模式有四種,分別是:ALPHA_8:每個畫素佔用 1byte 記憶體、ARGB_4444:每個畫素佔用 2byte 記憶體、ARGB_8888:每個畫素佔用 4byte 記憶體、RGB_565:每個畫素佔用 2byte 記憶體。Android預設的顏色模式為ARGB_8888,這個顏色模式色彩最細膩,顯示質量最高。

但同樣的,佔用的記憶體也最大。以上程式碼即是將圖片資源以 RGB_565 (或以 ARGB_4444)模式讀出。記憶體減少雖然不如第一種方法明顯,但是對於大多數圖片,看不出與 ARGB_8888 模式有什麼差別。不過在讀取有漸變效果的圖片時,可能有顏色條出現。另外,會影響圖片的特效處理。



4. 使用 Matrix 物件放大的圖片如何更改顏色模式:

雖然使用 Matrix 物件放大圖片,必定會耗費更多的記憶體,但有時候也不得不這樣做。放大後的圖片使用的 ARGB_8888 顏色模式,就算原圖片是ARGB_4444 顏色模式也一樣,而且沒有辦法在放大時直接指定顏色模式。可以採用以下辦法更改圖片顏色模式。

程式碼如下:

Matrix matrix = new Matrix();
float newWidth = 200;       // 圖片放大後的寬度
float newHeight = 300;      // 圖片放大後的長度
matrix.postScale(newWidth / img.getWidth(), newHeight/ img.getHeight());
Bitmap img1 = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);// 得到放大圖片
img2 = img1.copy(Bitmap.Config.ARGB_4444, false);                  // 得到 ARGB_4444 顏色模式的圖片
img = null;
img1 = null;

這裡比起本來的圖片額外生成了一個圖片物件 img1。然則體系會主動收受接管 img1,所以實際記憶體還是削減了。

歸結起來還是以縮圖模式讀取圖片和削減圖片中每個畫素佔用的記憶體最為有效。 這兩種辦法固然有效,然則也有各自的弊病。

實際開闢中還是應當按照景象酌情應用。最王道的辦法,還是避免垃圾物件的產生。

例如在 ListView 的應用中,複用 convertView 等。若是應用 AsyncTask 載入圖片,要及時將引用的 ImageView 物件置為 null。

因為 AsyncTask 是用執行緒池實現的,所以此中引用的物件可能會擁有很長的生命週期,造成 GC 無法開釋。

我還是信賴 Android 的記憶體收受接管機制的,recycle 什麼的固然必然程度上有效,但總感覺不合適 Java 記憶體收受接管的原則。