Android中的內部類引起的記憶體洩露
引子
什麼是內部類?什麼是記憶體洩露?為什麼Android的內部類容易引起記憶體洩露?如何解決?
什麼是內部類?
什麼是內部類?什麼又是外部類、匿名類、區域性類、頂層類、巢狀類?大家可以參考我這篇文章 ,再查查一些資料,先弄清楚什麼是內部類和內部類的特性再向下看。
經常會遇見Android程式中這樣使用handler:
public class SomeActivity { // ...... private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what) { case 0: // do something break; case 1: // do something break; default: break; } } }; private void someMethod () { mHandler.sendEmptyMessage(0); } }
上述程式碼中,mHandler欄位指向一個匿名Handler類。匿名類是內部類嗎?匿名類會持有外部類的物件嗎? 答案是:匿名類是內部類,但是是特殊的內部類,如果把匿名類放到一個static方法中,它是不會持有外部類例項的。而在上面的程式碼中,這個mHandler會持有外部類(SomeActivity)例項的引用,因為它處於一個物件的上下文中,而不是型別上下文中。
什麼是”持有外部類例項的引用“?你可以這麼理解:
public class InnerClass { private OuterClass outer; public InnerClass(OuterClass outer) { this.outer = outer; } }
就是說,建立InnerClass物件的時,必須傳遞進去一個OuterClass物件,賦值給InnerClass的一個欄位outer,該欄位是OuterClass物件的引用。回憶一下GC原理,如果InnerClass物件沒有被標記為垃圾物件,那麼outer指向的OuterClass物件會可能被標記為垃圾物件嗎?答案是:InnerClass物件與GC Root有引用路徑,InnerClass物件又引用了OuterClass物件,那麼OuterClass物件到GC Root也是有引用路徑的,所以,OuterClass不可能是垃圾物件。
為什麼發生記憶體洩露?
由上文可以看出:當mHandler沒有被回收時,其外圍Activity物件不能被回收。當Activity被使用者關閉(finish),而此時mHandler還未被回收,那麼Activity物件就不會被回收,造成Activity記憶體洩露。
問題的關鍵轉入到了這個問題:為什麼Activity被finish了,mHandler還不能被回收?
傳送訊息時,我們使用了這個函式:mHandler.sendEmptyMessage(0)函式。通過檢視原始碼追蹤呼叫關係,發現走到了:
Message物件有個target欄位,該欄位是Handler型別,引用了當前Handler物件。一句話就是:你通過Handler發往訊息佇列的Message物件持有了Handler物件的引用。假如Message物件一直在訊息佇列中未被處理釋放掉,你的Handler物件就不會被釋放,進而你的Activity也不會被釋放。
這種現象很常見,當訊息佇列中含有大量的Message等待處理,你發的Message需要等幾秒才能被處理,而此時你關閉Activity,就會引起記憶體洩露。如果你經常send一些delay的訊息,即使訊息佇列不繁忙,在delay到達之前關閉Activity也會造成記憶體洩露。
有什麼解決方案?
方案#1:在關閉Activity時(finish/onStop等函式中),取消還在排隊的Message:mHandler.removeCallbacksAndMessages(null);
方案#2:使用WeakReference截斷StrongReference。問題的癥結既然是內部類持有外部類物件的引用,那我不用內部類就行了,直接使用靜態成員類。但mHandler又需要與Activity物件互動,那就來個WeakReference,指向外部Activity物件。
public class SomeActivity { private Handler mHandler = new MyHandler(this); private static class MyHandler extends Handler { private WeakReference<SomeActivity> ref; public MyHandler(SomeActivity activity) { if (activity != null) { ref = new WeakReference<SomeActivity>(activity); } } @Override public void handleMessage(Message msg) { if (ref == null) { return; } SomeActivity v = ref.get(); if (v == null) { return; } // handle message } } }
當Activity想關閉銷燬時,mHandler對它的弱引用沒有影響,該銷燬銷燬;當mHandler通過WeakReference拿不到Activity物件時,說明Activity已經銷燬了,就不用處理了,相當於丟棄了訊息。
另外,當你使用Handler有記憶體洩露時候,Android Studio的Lint會有如下提示:
(原發表在公司內部)