Adnroid原始碼學習筆記:Handler 執行緒間通訊

揚州慢發表於2019-02-28

常見的使用Handler執行緒間通訊:

//主執行緒:
Handler handler = new Handler() {
@Override
	public void handleMessage(Message msg) {
		...
	}
};

//子執行緒:
Message message = new Message();  
message.arg1 = 1;  
Bundle bundle = new Bundle();  
bundle.putString("test", "test");  
message.setData(bundle);  
handler.sendMessage(message); 
複製程式碼

這類操作一般用於在子執行緒更新UI。在主執行緒建立一個handler,重寫handlermessage方法,然後在子執行緒裡傳送訊息,主執行緒裡就會接受到訊息。這就是簡單的執行緒間通訊。

如果在子執行緒建立handler物件則會報錯。根據Log提示,子執行緒建立handler需要呼叫Looper.prepare() (在main函式中已經呼叫了Looper.prepareMainLooper(),該方法內會調起Looper.prepare()),Looper.loop()方法 。但是即使子執行緒呼叫Looper.prepare()建立Looper物件,這個Looper也是子執行緒的,不可以用於更新UI操作。

那到底Handler、Looper這幾個類之間是如何工作的呢?我們從源頭看起,以下是Looper類的prepare()方法:

public final class Looper {	
	...
	final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //threadLocal是執行緒內部的資料儲存類,該類儲存了執行緒的所有資料資訊。

	public static void prepare() {
		prepare(true);
	}
	
	private static void prepare(boolean quitAllowed) {
		if (sThreadLocal.get() != null) {
			throw new RuntimeException("Only one Looper may be created per thread");
		}
		sThreadLocal.set(new Looper(quitAllowed)); //建立一個Looper(Looper的構造器裡也建立了一個MessageQueue,),將Looper與執行緒關聯起來
	}

	public static @Nullable Looper myLooper() { //下面會看到的,設定Handler類裡的Looper時會呼叫該方法
		return sThreadLocal.get(); //獲得Looper物件
	}
	...
}
複製程式碼

當handler傳輸message時,不論是呼叫sendMessage(Message msg)還是sendMessageDelayed(),最後都會指向sendMessageAtTime()方法:

public class Handler {    
	...
	public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
		MessageQueue queue = mQueue;
		if (queue == null) {
			RuntimeException e = new RuntimeException(
				this + " sendMessageAtTime() called with no mQueue");
	            Log.w("Looper", e.getMessage(), e);
	            return false;
	        }
		return enqueueMessage(queue, msg, uptimeMillis);
	}
	...
}
複製程式碼

mQueue即訊息佇列,用於將收到的訊息以佇列形式排列,提供出隊和入隊方法,該變數是Looper的成員變數,在Handler建立時賦值給handler

public class Handler {
	...
	final Looper mLooper;
	final MessageQueue mQueue;
	mLooper = Looper.myLooper();  //建立Handler前呼叫Looper.prepare()時定義並設定了Looper,這裡呼叫Looper.myLooper()來獲得該Looper
	mQueue = mLooper.mQueue;
	...
}
複製程式碼

上面呼叫的enqueueMessage(queue, msg, uptimeMillis)方法作用是訊息入隊

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	msg.target = this; //把handler本身賦值給要入隊的訊息,用來待會兒出隊使用
	if (mAsynchronous) {
		msg.setAsynchronous(true);
	}
	return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼

可以看到最後是由訊息佇列queue呼叫自身MessageQueue類的入隊方法enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
	if (msg.target == null) {
		throw new IllegalArgumentException("Message must have a target.");
	}
	if (msg.isInUse()) {
		throw new IllegalStateException(msg + " This message is already in use.");
	}

	synchronized (this) {
		if (mQuitting) {
			IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
			Log.w(TAG, e.getMessage(), e);
			msg.recycle();
			return false;
		}

		msg.markInUse();
		msg.when = when;
		Message p = mMessages;
		boolean needWake;
		if (p == null || when == 0 || when < p.when) {
			// New head, wake up the event queue if blocked.
			msg.next = p;
			mMessages = msg;
			needWake = mBlocked;
		} else {
			// Inserted within the middle of the queue.  Usually we don`t have to wake
			// up the event queue unless there is a barrier at the head of the queue
			// and the message is the earliest asynchronous message in the queue.
			needWake = mBlocked && p.target == null && msg.isAsynchronous();
			Message prev;
			for (;;) {
				prev = p;
				p = p.next;
				if (p == null || when < p.when) {
					break;
				}
				if (needWake && p.isAsynchronous()) {
					needWake = false;
				}
			}
			msg.next = p; // invariant: p == prev.next
			prev.next = msg;
		}

		// We can assume mPtr != 0 because mQuitting is false.
		if (needWake) {
			nativeWake(mPtr);
		}
	}
	return true;
}
複製程式碼

mMessages是MessageQueue類的一個成員變數,用以記錄排在最前面的訊息,msg是我們傳入的message,msg.next是Message類的成員變數,可以理解成下一條訊息。入列方法重點看這幾句:

Message p = mMessages;
msg.next = p;  //把mMessages賦值給新入隊的這條訊息的next
mMessages = msg;  //把新入隊的訊息賦值給mMessages
複製程式碼

就像排隊一樣,msg是來插隊的,排第一的mMessages自願排到msg的後面,並讓msg站到自己原來的位置上,這樣就完成的msg的入隊操作,整個訊息入隊操作是按照時間來排序的。至於出隊操作,就在一開始所提到的ActivityTread中的main方法裡呼叫的Looper.loop()方法裡:

public static void loop() {
	...

	for (;;) {
		...
		Message msg = queue.next(); //獲取下一條訊息
		...
		msg.target.dispatchMessage(msg); //傳遞訊息
        ...
		msg.recycleUnchecked(); //清空狀態,迴圈往復
        }
    }
    ...
}
複製程式碼

提煉出來就是在loop方法裡一直死迴圈,從MessageQueue訊息佇列裡使用next()方法獲得下一條訊息,next方法簡單看就是:

Message msg = mMessages;
mMessages = msg.next;
msg.next = null;
return msg;
複製程式碼

這就是簡單的解釋訊息出列,把排第一的訊息作為方法的返回值,然後讓排第二的排到第一去。獲得訊息後使用msg.target(上面入隊時賦值的handler)來傳遞訊息:

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
		handleCallback(msg); //如果有callback引數則呼叫處理回撥的方法
	} else {
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) {
				return;
            }
		}
		handleMessage(msg); //將訊息作為引數傳遞出去
	}
}
複製程式碼

至此,handler傳遞訊息的整個流程走完。另外還有一個我們經常用到handler的方法post:

public final boolean post(Runnable r) {
	return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) { //將runnable變成message自身的callback變數
	Message m = Message.obtain();
	m.callback = r;
	return m;
}
複製程式碼

可以看到,post的runnable引數經過getPostMessage()方法最後被賦值給要傳遞下去的訊息的callback這個變數,等到訊息出列時,如果訊息帶有callback引數則呼叫處理回撥的方法handleCallback(msg)

private static void handleCallback(Message message) {
	message.callback.run();
}
複製程式碼

可以看到,不論是從sendMessage裡發出的訊息,還是在post傳遞的runnable裡執行的程式碼,最後都是殊途同歸,都是在UI執行緒執行的。最後總結一下吧,執行緒間通訊原理大概就是:

  1. Looper.prepare()建立Looper和MessageQueue,並與所線上程關聯
  2. Looper.loop()通過一個for死迴圈不斷對MessageQueue進行輪詢
  3. 建立handler時,會把Looper和MessageQueue賦值給handler,將三者關聯起來。當handler呼叫sendMessage傳遞訊息,訊息會被髮送到Looper的訊息佇列MessageQueue裡
  4. 一旦loop()方法接收到訊息,則將訊息通過該訊息攜帶的handler(msg.target)的handleMessage方法處理

相關文章