從Android Handler內部類到WeakReference的知識關聯

weixin_34162629發表於2016-03-02

Handler:

普通使用方法:

Handler用於處理和從佇列MessageQueue中得到Message。一般我們要重寫Handler的handleMessage(Message msg){}方法來處理,例如以下程式碼:

public class MainActivity extends Activity {
    private TextView textView;
    
	Handler normalHandler = new Handler(){
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case 1:
				Log.i("test",textView.getText().toString());
				break;

			default:
				break;
			}
		};
	};
}



問題:

這個時候Handler會被Android SDK中Lint工具檢查警告你(左邊那個黃色燈泡+歎號):This Handler class should be static or leaks might occur 

原因:

This Handler class should be static:

(知識點一)為什麼靜態內部類能夠解決問題呢?或者說靜態內部類和非靜態內部類的差別是什麼?

舉例:class A{int a; static int b class B{}  static class C{} }  (A是外部類,B非靜態內部類,C靜態內部類,a普通欄位。b靜態欄位)

1)B非靜態內部類:

能夠訪問A.a和A.b,也就是外部的屬性都能方位。

由於B隱式的持有A類物件的引用。相當於A的屬性

2)C靜態內部類:

C僅僅能夠訪問A.b,不能夠方位A.a。為什麼?由於C不含有A的引用。它和A類是同一個級別。僅僅只是寫到了A類的內部。

本例原因:

Handler匿名內部類,隱式的持有了外部類Activity的引用(這就是為什麼你能在handleMessage()中呼叫MainActivity中TextView等的屬性)。

--->而以後調

Message message = normalHandler.obtainMessage();
normalHandler.sendMessageAtTime(message , 100*1000);

得到的message中又含有這個Handler的引用(能夠看原始碼)。

在100秒後message被執行,這期間message被放在MessageQueue中,MessageQueue在Looper中,Looper是執行緒的本地變數。

也就是說MainActivity即使生命週期走完了也不會垃圾回收,為什麼?由於Java的垃圾回收機制,就是看一個物件有沒有被引用(從執行緒中的主要物件開始,物件之間的引用形成網狀結構,假設有類的物件不在這張網上,就證明它沒被引用。這就是資料結構中圖的遍歷,什麼連通子圖,非連通子圖)。

而本文中一個MainActivity被Handler持有引用。Handler被Message持有引用,Message被MessageQueue持有引用,MessageQueue被Looper持有引用,Looper為執行緒本地變數,執行緒不被摧毀。它就不會被銷燬。

所以即便使用者已經切換、退出到別的Activity。MainActivity佔有的記憶體仍舊不會被釋放。


解決方式:

打破引用鏈:

1.Message在100秒後被處理。之後回收Message,然後回收MainActivity。

(所以是實際上,你僅僅要不發非常長時間的Message也不會有什麼問題)

2.使Handler不持有MainActivity的引用,用弱引用WeakReference:(簡單講,就是僅僅有WeakReference引用的物件。垃圾回收將回收該物件,以後再另寫一篇引用的文章吧)


正常程式碼:

	MyHandler handler = new MyHandler(this);

	public static class MyHandler extends Handler {
		private WeakReference<MainActivity> reference;

		public MyHandler(MainActivity activity) {
			reference = new WeakReference<MainActivity>(activity);
		}

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case 1:
				Log.i("test",textView.getText().toString());
				break;

			default:
				break;
			}
		}
	}



相關文章