這也是
Android
中老生常談的一個話題了,它本身並不是很複雜,可是面試官比較喜歡問。本文就從原始碼再簡單的理一下這個機制。也可以說是理一下Handler
、Looper
、MessageQueue
之間的關係。
單執行緒中的訊息處理機制的實現
首先我們以Looper.java
原始碼中給出的一個例子來分析一下在單執行緒中如何使用這個機制:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
複製程式碼
Looper.prepare()
上面只涉及到Handler
和Looper
,MessageQueue
呢?我們來看一下Looper.prepare()
:
private static void prepare(boolean quitAllowed) {
...
sThreadLocal.set(new Looper(quitAllowed));
}
複製程式碼
很簡單,即new Looper
,然後把它放到ThreadLocal<Looper>
中(ThreadLocal儲存線上程的私有map中)。繼續看一下Looper的構造方法
:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
...
}
複製程式碼
即,在這裡MessageQueue
作為Looper
的成員變數被初始化了。所以 一個Looper對應一個MessageQueue 。 ok,到這裡Looper.prepare()
所涉及的邏輯以及瀏覽完畢,繼續看一下new Handler()
:
new Handler()
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can`t create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
複製程式碼
Handler
會持有一個Looper
, 那我們看一下這個Looper
來自於哪裡: Looper.myLooper()
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製程式碼
即會從當前執行緒的私有map中取出ThreadLocal<Looper>
。所以Handler
預設持有當前執行緒的Looper
的引用。如果當前執行緒沒有Looper
,那麼Handler
就會構造失敗,丟擲異常。其實可以在構造Handler
時指定一個Looper
,下面會講到這個。
在持有當前執行緒的Looper
的引用同時,Handler
在構造時也會獲取Looper
的成員變數MessageQueue
,並持有它。 如果你在一個執行緒中同時new多個Handler
的話,那他們的關係如下圖所示:
即:
Looper
和MessageQueue
存放在當前執行緒的ThreadLocal
中Handler
持有當前執行緒的Looper
和MessageQueue
。
Looper.loop()
這個方法可以說是核心了:
public static void loop( ) {
final Looper me = myLooper();
//...
final MessageQueue queue = me.mQueue;
//...
for (;;) {
Message msg = queue.next(); // might block
//…
msg.target.dispatchMessage(msg);
//...
}
}
複製程式碼
即Looper
不斷檢查MessageQueue
中是否有訊息Message
,並呼叫msg.target.dispatchMessage(msg)
處理這個訊息。 那msg.target
是什麼呢?其實它是Handler
的引用。
msg.target.dispatchMessage(msg)
會導致Handler.handleMessage()
的呼叫,其實到這裡單執行緒中的訊息處理機制模型
已經有了一個大致的輪廓了,接下來就需要弄清楚
msg.target
是在哪裡賦值的?- 訊息是如何插入到
MessageQueue
中的?
傳送一個訊息
以Handler
傳送一個最簡單的訊息為例:
handler.sendEmptyMessage(0)
複製程式碼
這個方法最終呼叫到的核心邏輯是:
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
即Handler
會把Message
的target
設定為自己,並把訊息放入到當前執行緒的MessageQueue
中。
這樣Looper.loop()
方法中就可以從MessageQueue
中取出訊息,並把訊息傳送到msg.target(Handler)
去處理,其實msg.target.dispatchMessage(msg)
會呼叫Handler.handleMessage()
方法。
到這裡就完成了整個訊息處理模型
的分析。其實 整個Android主執行緒的UI更新
都是建立在這個模型之上的
用下面這個圖總結一下整個執行機制:
多執行緒中的訊息處理機制的應用
舉一個最典型的例子: 在下載執行緒中完成了下載,通知主執行緒更新UI。怎麼做呢?
明白了上面Handler/MessageQueue/Looper
的關係後,我們只需要往主執行緒的MessageQueue
中傳送一個更新UI的Message
即可,那怎麼往主執行緒發訊息呢?
指定Handler所依附的Looper
對,只需要在構造Hander時把主執行緒的Looper傳遞給它即可:
downLoadHandler = Handler(Looper.getMainLooper())
複製程式碼
這樣downLoadHandler.sendEmptyMessage(2)
就會傳送到主執行緒的MessageQueue
中。handler.handleMessage()
也將會在主執行緒中回撥。
其實更簡單的是在主執行緒中儲存一個Handler
成員變數,子執行緒直接拿這個Handler
發訊息即可。
但在多執行緒使用Handler
時因為涉及到執行緒切換和非同步,要注意記憶體洩漏和物件可用性檢查。比如在更新UI時Activity
是finish
狀態,這時候就需要你的update
是否可以繼續執行等。
執行緒通訊
運用這個模型我們可以很方便的進行執行緒通訊:
- 需要通訊的兩個執行緒都建立
Looper
- 雙方分別持有對方的handler,然後互相傳送訊息
歡迎關注我的Android進階計劃看更多幹貨
微信公眾號: