1) 要及時回收Bitmap的記憶體
Bitmap類有一個方法recycle(),從方法名可以看出意思是回收。這裡就有疑問了,Android系統有自己的記憶體回收機制,可以不週期性回收掉不使用的記憶體空間,當然也包括Bitmap的空間。那為什麼還需要這個方法呢?
Bitmap類的構造方法都是私人的,所以開發人員不能直接new出一個Bitmap對象,只能通過BitmapFactory類的各種靜態方法來執行個體化一個Bitmap。仔細查看BitmapFactory的原始碼可以看到,產生Bitmap對象最終都是通過JNI調用方式實現的。所以,載入Bitmap到記憶體裡以後,是包含兩部分記憶體地區的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap對象是由Java部分分配的,不用的時候系統就會自動回收了,但是那個對應的C可用的記憶體地區,虛擬機器是不能直接回收的,這個只能調用底層的功能釋放。所以需要調用recycle()方法來釋放C部分的記憶體。從Bitmap類的原始碼也可以看到,recycle()方法裡也的確是調用了JNI方法了的。
那如果不調用recycle(),是否就一定存在記憶體泄露呢?也不是的。Android的每個應用都運行在獨立的進程裡,有著獨立的記憶體,如果整個進程被應用本身或者系統殺死了,記憶體也就都被釋放掉了,當然也包括C部分的記憶體。
Android對於進程的管理是非常複雜的。簡單的說,Android系統的進程分為幾個層級,系統會在記憶體不足的情況下殺死一些低優先順序的進程,以提供給其它進程充足的記憶體空間。在實際項目開發過程中,有的開發人員會在退出程式的時候使用Process.killProcess(Process.myPid())的方式將自己的進程殺死,但是有的應用僅僅會使用調用Activity.finish()方法的方式關閉掉所有的Activity。
經驗分享:
Android手機的使用者,根據習慣不同,可能會有兩種方式退出整個應用程式:一種是按Home鍵直接退到案頭;另一種是從應用程式的退出按鈕或者按Back鍵退出程式。那麼從系統的角度來說,這兩種方式有什麼區別呢?按Home鍵,應用程式並沒有被關閉,而是成為了後台應用程式。按Back鍵,一般來說,應用程式關閉了,但是進程並沒有被殺死,而是成為了空進程(程式本身對退出做了特殊處理的不考慮在內)。
Android系統已經做了大量進程管理的工作,這些已經可以滿足使用者的需求。個人建議,應用程式在退出應用的時候不需要手動殺死自己所在的進程。對於應用程式本身的進程管理,交給Android系統來處理就可以了。應用程式需要做的,是盡量做好程式本身的記憶體管理工作。
一般來說,如果能夠獲得Bitmap對象的引用,就需要及時的調用Bitmap的recycle()方法來釋放Bitmap佔用的記憶體空間,而不要等Android系統來進行釋放。
下面是釋放Bitmap的範例程式碼片段。
複製代碼 代碼如下:// 先判斷是否已經回收
if(bitmap != null && !bitmap.isRecycled()){
// 回收並且置為null
bitmap.recycle();
bitmap = null;
}
System.gc();
從上面的代碼可以看到,bitmap.recycle()方法用於回收該Bitmap所佔用的記憶體,接著將bitmap置空,最後使用System.gc()調用一下系統的記憶體回收行程進行回收,可以通知記憶體回收行程儘快進行回收。這裡需要注意的是,調用System.gc()並不能保證立即開始進行回收過程,而只是為了加快回收的到來。
如何調用recycle()方法進行回收已經瞭解了,那什麼時候釋放Bitmap的記憶體比較合適呢?一般來說,如果代碼已經不再需要使用Bitmap對象了,就可以釋放了。釋放記憶體以後,就不能再使用該Bitmap對象了,如果再次使用,就會拋出異常。所以一定要保證不再使用的時候釋放。比如,如果是在某個Activity中使用Bitmap,就可以在Activity的onStop()或者onDestroy()方法中進行回收。
2) 捕獲異常
因為Bitmap是吃記憶體大戶,為了避免應用在分配Bitmap記憶體的時候出現OutOfMemory異常以後Crash掉,需要特別注意執行個體化Bitmap部分的代碼。通常,在執行個體化Bitmap的代碼中,一定要對OutOfMemory異常進行捕獲。
以下是程式碼範例。
複製代碼 代碼如下:Bitmap bitmap = null;
try {
// 執行個體化Bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
//
}
if (bitmap == null) {
// 如果執行個體化失敗 返回預設的Bitmap對象
return defaultBitmapMap;
}
這裡對初始化Bitmap對象過程中可能發生的OutOfMemory異常進行了捕獲。如果發生了OutOfMemory異常,應用不會崩潰,而是得到了一個預設的Bitmap圖。
經驗分享:
很多開發人員會習慣性的在代碼中直接捕獲Exception。但是對於OutOfMemoryError來說,這樣做是捕獲不到的。因為OutOfMemoryError是一種Error,而不是Exception。在此僅僅做一下提醒,避免寫錯代碼而捕獲不到OutOfMemoryError。
3) 緩衝通用的Bitmap對象
有時候,可能需要在一個Activity裡多次用到同一張圖片。比如一個Activity會展示一些使用者的頭像列表,而如果使用者沒有設定頭像的話,則會顯示一個預設頭像,而這個頭像是位於應用程式本身的資源檔中的。
如果有類似上面的情境,就可以對同一Bitmap進行緩衝。如果不進行緩衝,儘管看到的是同一張圖片檔案,但是使用BitmapFactory類的方法來執行個體化出來的Bitmap,是不同的Bitmap對象。緩衝可以避免建立多個Bitmap對象,避免記憶體的浪費。
經驗分享:
Web開發人員對於緩衝技術是很熟悉的。其實在Android應用開發過程中,也會經常使用緩衝的技術。這裡所說的緩衝有兩個層級,一個是硬碟緩衝,一個是記憶體緩衝。比如說,在開發網路應用過程中,可以將一些從網路上擷取的資料儲存到SD卡中,下次直接從SD卡讀取,而不從網路中讀取,從而節省網路流量。這種方式就是硬碟緩衝。再比如,應用程式經常會使用同一對象,也可以放到記憶體中緩衝起來,需要的時候直接從記憶體中讀取。這種方式就是記憶體緩衝。
4) 壓縮圖片
如果圖片像素過大,使用BitmapFactory類的方法執行個體化Bitmap的過程中,需要大於8M的記憶體空間,就必定會發生OutOfMemory異常。這個時候該如何處理呢?如果有這種情況,則可以將圖片縮小,以減少載入圖片過程中的記憶體的使用,避免異常發生。
使用BitmapFactory.Options設定inSampleSize就可以縮小圖片。屬性值inSampleSize表示縮圖大小為原始圖片大小的幾分之一。即如果這個值為2,則取出的縮圖的寬和高都是原始圖片的1/2,圖片的大小就為原始大小的1/4。
如果知道圖片的像素過大,就可以對其進行縮小。那麼如何才知道圖片過大呢?
使用BitmapFactory.Options設定inJustDecodeBounds為true後,再使用decodeFile()等方法,並不會真正的分配空間,即解碼出來的Bitmap為null,但是可計算出原始圖片的寬度和高度,即options.outWidth和options.outHeight。通過這兩個值,就可以知道圖片是否過大了。
複製代碼 代碼如下: BitmapFactory.Options opts = new BitmapFactory.Options();
// 設定inJustDecodeBounds為true
opts.inJustDecodeBounds = true;
// 使用decodeFile方法得到圖片的寬和高
BitmapFactory.decodeFile(path, opts);
// 列印出圖片的寬和高
Log.d("example", opts.outWidth + "," + opts.outHeight);
在實際項目中,可以利用上面的代碼,先擷取圖片真實的寬度和高度,然後判斷是否需要跑縮小。如果不需要縮小,設定inSampleSize的值為1。如果需要縮小,則動態計算並設定inSampleSize的值,對圖片進行縮小。需要注意的是,在下次使用BitmapFactory的decodeFile()等方法執行個體化Bitmap對象前,別忘記將opts.inJustDecodeBound設定回false。否則擷取的bitmap對象還是null。
經驗分享:
如果程式的圖片的來源都是程式包中的資源,或者是自己伺服器上的圖片,圖片的大小是開發人員可以調整的,那麼一般來說,就只需要注意使用的圖片不要過大,並且注意代碼的品質,及時回收Bitmap對象,就能避免OutOfMemory異常的發生。
如果程式的圖片來自外界,這個時候就特別需要注意OutOfMemory的發生。一個是如果載入的圖片比較大,就需要先縮小;另一個是一定要捕獲異常,避免程式Crash。