1. 程式人生 > >Android中的內部類引起的記憶體洩露

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會有如下提示:

(原發表在公司內部)