一忽刽、Handler涯雅、MessageQueue外构、Looper 的關系
-
模型職責
- Handler: 負責向MQ里入隊消息(sendMessage)、刪除消息(removeMessage)域那、處理消息(handleMessage)
- MessageQueue: 負責投遞消息(enqueueMessage)蜕依,取消息(next)
- Looper: 負責輪詢MQ,將取出的消息分發(fā)給對應的 handler 處理(loop)
- Message: 單鏈表結構(next)琉雳,綁定目標Handler(target)样眠,記錄發(fā)送時間(when),發(fā)送內容(obj)
-
調用關系
- Handler: 需要指定 Looper翠肘,Looper 中會有對應的 MQ
- MessageQueue: 對應一個待處理的 Message 鏈表
- Looper: 對應一個 MQ
- Message: 對應處理自己的 Handler
Handler 消息隊列機制的使用過程如下
```
class HandlerThread extends Thread {
private Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler(Looper.myLooper());
Looper.loop();
}
public Handler getThreadHandler() {
return mHandler;
}
}
class ActivityThread {
public static void main() {
//...
HandlerThread ht = new HandlerThread();
ht.start();
ht.getThreadHandler().post(
()-> {
//do xxx
}
);
}
}
```
-
關系確立時機
-
Handler Looper
class Handler { public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } }
初始化 Handler 的時候檐束,會指定 Looper,同時會將 Looper 中的 MessageQueue 也綁定到Handler上
-
Handler Message
class Handler { private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } }
在發(fā)送消息的時候束倍,Handler 會和 Message 發(fā)生綁定被丧,
msg.target=this
,可以理解為誰發(fā)的消息就要誰去處理 -
Looper Thread
class Looper { 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)); } }
sThreadLocal.set(new Looper(quitAllowed))
绪妹,借助 ThreadLocal 將 Looper 和 Thread 進行綁定甥桂,所以一個 Thread 只會對應一個 Looper,是 ThreadLocal 決定的邮旷,想知道ThreadLocal原理的同學可以自己查查相關資料黄选。
-
二、有關消息隊列的一些問題
- 如何保障消息隊列中的消息的時間順序婶肩?
- 延時消息如何實現(xiàn)的?
- 消息的分發(fā)過程?
- 消息屏障是什么办陷?
解決這些問題的關鍵在于MQ入隊和出隊消息的邏輯
首先,解決前兩個問題律歼,如何保障消息隊列中的消息的時間順序民镜?延時消息如何實現(xiàn)的?
這需要分析enqueueMessage()
boolean enqueueMessage(Message msg, long when)
這個方法是在Handler#sendMessageAtTime()
中調用的,when
是SystemClock.uptimeMillis() + delayMillis
的結果险毁,即系統(tǒng)正常運行時間(不算阻塞制圈、休眠)+消息延時時長
去除特殊情況们童,和加鎖邏輯后的代碼如下
boolean enqueueMessage(Message msg, long when) {
msg.when = when;
Message p = mMessages;//之前的消息鏈
if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.
msg.next = p; //新 msg 作為鏈表頭
mMessages = msg; //更新消息鏈
needWake = mBlocked;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) { //走到鏈表尾部或找到正確時間線位置
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg; //將msg插入到消息鏈
}
}
具體插入邏輯簡化為,向隊列頭部插入消息鲸鹦,比如調用了handler#sendMessageAtFrontOfQueue
慧库,按時間線插入消息,如下圖所示亥鬓,假設系統(tǒng)當前運行時間定格在+500的狀態(tài),開始向隊列插入消息的狀況
特殊說明: 在+4100插入了一個延時500的消息域庇,在入隊的時候需要調整鏈表位置嵌戈,所以+4100的消息會插入到+4200的后面,保證了消息的時間順序听皿,同時巧妙的實現(xiàn)了延時消息熟呛。
然后,分析一下取出正常消息(同步消息)的過程尉姨,精簡代碼如下
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); //阻塞 nextPollTimeoutMillis 時間
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) { //沒到發(fā)消息的時間庵朝,隊列需要阻塞
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); //此時會給 nextPollTimeoutMillis 賦值,在循環(huán)開始時會阻塞
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else { //調整消息鏈表
mMessages = msg.next;
}
msg.next = null; //斷開后面的消息鏈
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); //標記為消息已使用
return msg;// 返回得到的消息
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
}
}
正常從消息隊列取消息的過程可以理解為如下過程
假設系統(tǒng)時間為+4300
又厉,此時取出next
為鏈表中的最后一個消息九府,when=+4500
,此時因為還未到執(zhí)行消息的時候覆致,就會給nextPollTimeoutMillis
賦值侄旬,然后會檢查pendingIdleHandlerCount
,此時一定沒有IdleHandler要處理任務煌妈,進入下一次循環(huán)儡羔,進入阻塞狀態(tài)
第三個問題,消息的分發(fā)過程?
在上面剛剛分析了取消息的過程 MessageQueue#next()
璧诵,現(xiàn)在看看是誰調用了 next 并且得到了當前的 Message 對象汰蜘,這個問題答案在Looper#loop()
中,簡化邏輯后的代碼如下
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
}
}
邏輯很簡單之宿,就是死循環(huán)調用next()族操,如果得到msg,就調用 msg.target.dispatchMessage 即回到 Handler#dispatchMessage
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
邏輯也很簡單比被,就是指定了一個回調的優(yōu)先級坪创,先看 msg 是否指定了 Callback,如果沒有就查看是否給 Handler 設置了 Callback姐赡,如果也沒有就執(zhí)行 Handler#handleMessage
莱预,這個方法需要自己重寫
第四,消息屏障是什么?
簡單來說项滑,消息屏障就是個標志依沮,標志開啟時會優(yōu)先處理異步消息
這個問題也需要在消息的入隊出隊時尋找答案,由于應用開發(fā)的時候使用場景比較少,所以放到最后解答危喉,現(xiàn)在看一下有關消息屏障的源碼
Message next() {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {// Stalled by a barrier. Find the next asynchronous message in the queue.
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//后面走上面分析過的流程宋渔,但此時的msg已經(jīng)是一個異步消息了
}
判斷有無消息屏障的條件是 msg.target == null
,在有消息屏障下辜限,會不斷循環(huán)鏈表找到異步消息皇拣,條件為!msg.isAsynchronous()
,現(xiàn)在再來看看如何設置消息屏障MessageQueue#postSyncBarrier
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
//此處沒設置target薄嫡,所以target是null
Message prev = null; //找到合適的位置插入屏障消息
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
這個方法就是在指定時間處插入一個屏障消息氧急,如果是0,就是在消息隊列最頭部插入毫深,然后消息隊列在取消息的時候發(fā)現(xiàn)有消息屏障就會向后遍歷直到找到異步消息(msg.isAsynchronous())吩坝,將消息分發(fā)處理。
個人理解:
消息屏障的作用其實并不是名字定義的同步異步的意思哑蔫,而是給消息定了優(yōu)先級钉寝,給異步消息開了后門(只要有屏障消息在,之后讀取的消息都是異步消息)闸迷。
三嵌纲、小結
- 消息隊列中的消息按時間順序排序和延時消息的實現(xiàn),都是依靠系統(tǒng)時間的推移實現(xiàn)的
- 如果下一個消息的時間超過系統(tǒng)當前時間腥沽,則會阻塞
- 屏障消息的判斷條件是
msg.target == null
疹瘦,正常使用Handler發(fā)的消息是無法將 target 置為 null 的,需要手動調用postSyncBarrier
補充:
IdleHandler的作用是什么巡球?
IdleHandler 可以用來提升提升性能言沐,主要用在我們希望能夠在當前線程消息隊列空閑時做些事情(譬如UI線程在顯示完成后,如果線程空閑我們就可以提前準備其他內容)的情況下酣栈,不過最好不要做耗時操作险胰。