Android導致記憶體洩漏的幾種情形
集合類
集合類如果僅僅有新增元素的方法,而沒有相應的刪除機制,導致記憶體被佔用。如果這個集合類是全域性性的變數(比如類中的靜態屬性,全域性性的map等即有靜態引用或final一直指向它),那麼沒有相應的刪除機制,很可能導致集合所佔用的記憶體只增不減。
單例模式
不正確使用單例模式是引起記憶體洩漏的一個常見問題,單例物件在被初始化後將在JVM的整個生命週期中存在(以靜態變數的方式),如果單例物件持有外部物件的引用,那麼這個外部物件將不能被JVM正常回收,導致記憶體洩漏。
如果需要Context,儘量引用Application,而不用Activity。
Android元件或特殊集合物件的使用
BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在Activity onDestory或者某類生命週期結束之後一定要unregister或者close掉,否則這個Activity類會被system強引用,不會被記憶體回收。不要直接對Activity進行直接引用作為成員變數,如果不得不這麼做,請用private WeakReference mActivity來做,相同的,對於Service等其他有自己生命週期的物件來說,直接引用都需要謹慎考慮是否會存在記憶體洩漏的可能。
Handler
Handler的生命週期與Activity不一致
- 由於Handler屬於TLS(Thread Local Storage)變數,生命週期和Activity是不一致的。
- 當Android應用啟動的時候,會先建立一個UI主執行緒的Looper物件,Looper實現了一個簡單的訊息佇列,一個一個的處理裡面的Message物件。主執行緒Looper物件在整個應用生命週期中存在。
- 當在主執行緒中初始化Handler時,該Handler和Looper的訊息佇列關聯(沒有關聯會報錯的)。傳送到訊息佇列的Message會引用傳送該訊息的Handler物件,這樣系統可以呼叫 Handler#handleMessage(Message) 來分發處理該訊息。
- 只要Handler傳送的Message尚未被處理,則該Message及傳送它的Handler物件將被執行緒MessageQueue一直持有。因此這種實現方式一般很難保證跟View或者Activity的生命週期保持一致,故很容易導致無法正確釋放。
handler引用Activity阻止了GC對Acivity的回收
- 在Java中,非靜態(匿名)內部類會預設隱性引用外部類物件。而靜態內部類不會引用外部類物件。
- 如果外部類是Activity,則會引起Activity洩露。
- 當Activity finish後,延時訊息會繼續存在主執行緒訊息佇列中1分鐘,然後處理訊息。而該訊息引用了Activity的Handler物件,然後這個Handler又引用了這個Activity。這些引用物件會保持到該訊息被處理完,這樣就導致該Activity物件無法被回收,從而導致了上面說的 Activity洩露。
如上所述,Handler的使用要尤為小心,否則將很容易導致記憶體洩漏的發生。
錯誤示例:
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
解決辦法:
使用顯形的引用,1.靜態內部類。 2. 外部類
使用弱引用 2. WeakReference
正確示例:
private static class MyHandler extends Handler {
private final WeakReference<HandlerActivity2> mActivity;
public MyHandler(HandlerActivity2 activity) {
mActivity = new WeakReference<HandlerActivity2>(activity);
}
@Override
public void handleMessage(Message msg) {
System.out.println(msg);
if (mActivity.get() == null) {
return;
}
mActivity.get().todo();
}
}
@Override
public void onDestroy() {
// If null, all callbacks and messages will be removed.
mHandler.removeCallbacksAndMessages(null);
}
ThreadHandler
HandlerThread handlerThread = new HandlerThread("test");
handlerThread.start(); //建立HandlerThread後一定要記得start()
獲取HandlerThread的Looper
Looper looper = handlerThread.getLooper();
建立Handler,通過Looper初始化
Handler handler = new Handler(looper);
通過以上三步我們就成功建立HandlerThread。通過handler傳送訊息,就會在子執行緒中執行。
如果我們在Activity的onCreate中進行上面的初始化,不再進行其他工作,那麼就有可能造成記憶體洩漏。
因為不過Activity finish後,程序沒有退出,那麼建立的test會一直存在。
解決辦法:
在onDestory中呼叫handlerThread.quit()或handlerThread.quitSafely();
Thread記憶體洩漏
執行緒也是造成記憶體洩漏的一個重要的源頭。執行緒產生的記憶體洩漏主要原因在於執行緒生命週期的不可控。比如執行緒是Activity的內部類,則執行緒物件中儲存了Activity的一個引用,當執行緒的run函式耗時較長沒有結束時,執行緒物件是不會被銷燬的,因此它引用的老的Activity也不會被銷燬,因此就出現了記憶體洩漏的問題。
一些不良程式碼造成的記憶體壓力
這些程式碼並不造成記憶體洩漏,但是它們,或是對沒使用的記憶體沒有進行有效及時的釋放,或是沒有有效的利用已有的物件而是頻繁的申請新記憶體。
Bitmap沒呼叫recycle()
Bitmap物件在不使用時,我們應該先呼叫recycle()釋放記憶體,然後將它設定為null。因為載入Bitmap物件的記憶體空間,一部分是java的,一部分C的(因為Bitmap分配的底層是通過JNI呼叫的)。而這個recycle()就是針對C部分的記憶體釋放。