做爲Android的基礎知識,消息機制已被無數人寫過。筆者曾經也寫過一篇深刻分析的文章,但整體而言乏善可陳,並沒有新穎之處。最近剛好從新整理了一下思路,想着能夠從細節的角度出發,對一些冷門的知識點作一個概括。記錄於此,供你們批評討論。html
本文全部代碼基於Android Q (10.0)
java
假設線程1此時正在處理一個消息,線程2經過以下方式(方式Ⅰ)往線程1的消息隊列中插入兩個消息。請問消息A和消息B哪一個先被處理呢?android
handler.sendMessage(msgA);
handler.sendMessage(msgB);
複製代碼
那若是是經過下面這種方式(方式Ⅱ),消息A和消息B又是哪一個先被處理呢?編程
handler.sendMessageAtFrontOfQueue(msgA);
handler.sendMessageAtFrontOfQueue(msgB);
複製代碼
答案是經過方式Ⅰ發送時,消息A先被處理;經過方式Ⅱ發送時,消息B先被處理。具體解釋以下:數組
/frameworks/base/core/java/android/os/Handler.javamarkdown
746 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, 747 long uptimeMillis) {
748 msg.target = this;
749 msg.workSourceUid = ThreadLocalWorkSource.getUid();
750
751 if (mAsynchronous) {
752 msg.setAsynchronous(true);
753 }
754 return queue.enqueueMessage(msg, uptimeMillis);
755 }
複製代碼
Handler類中全部的sendMessage方法,最終都是調用MessageQueue.enqueMessage方法將消息加入到隊列之中。調用時傳入兩個參數:msg和uptimeMillis。msg天然是想要發送的消息,而uptimeMillis則是消息預計發送的時間。多線程
SystemClock.uptimeMillis(): Returns milliseconds since boot, not counting time spent in deep sleep.app
/frameworks/base/core/java/android/os/Handler.javaless
610 public final boolean sendMessage(@NonNull Message msg) {
611 return sendMessageDelayed(msg, 0);
612 }
複製代碼
669 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
670 if (delayMillis < 0) {
671 delayMillis = 0;
672 }
673 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
674 }
複製代碼
695 public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
696 MessageQueue queue = mQueue;
697 if (queue == null) {
698 RuntimeException e = new RuntimeException(
699 this + " sendMessageAtTime() called with no mQueue");
700 Log.w("Looper", e.getMessage(), e);
701 return false;
702 }
703 return enqueueMessage(queue, msg, uptimeMillis);
704 }
複製代碼
最終傳入MessageQueue.enqueMessage的uptimeMillis是sendMessageDelayed中臨時獲取的當下時間。當消息B獲取到的uptime大於A時,B在隊列中必然插入到A的後面。可是因爲uptimeMillis的單位是毫秒,因此A和B徹底可能獲取到同樣的uptimeMillis(在一毫秒內完成兩次消息發送的動做)。若是兩者的uptimeMillis同樣,那麼他們的順序又該怎麼排列呢?異步
/frameworks/base/core/java/android/os/MessageQueue.java#enqueueMessage
569 if (p == null || when == 0 || when < p.when) {
570 // New head, wake up the event queue if blocked.
571 msg.next = p;
572 mMessages = msg;
573 needWake = mBlocked;
574 } else {
575 // Inserted within the middle of the queue. Usually we don't have to wake
576 // up the event queue unless there is a barrier at the head of the queue
577 // and the message is the earliest asynchronous message in the queue.
578 needWake = mBlocked && p.target == null && msg.isAsynchronous();
579 Message prev;
580 for (;;) {
581 prev = p;
582 p = p.next;
583 if (p == null || when < p.when) {
584 break;
585 }
586 if (needWake && p.isAsynchronous()) {
587 needWake = false;
588 }
589 }
590 msg.next = p; // invariant: p == prev.next
591 prev.next = msg;
592 }
複製代碼
一個消息如果加入到隊列中來,只多是兩種狀況:
對於第一種狀況,假設A先加入到隊列頭部,只有當B的when(uptime)小於A的when時,B纔會加入到A的前面;對於第二種狀況,一樣是隻有B的when小於A的when時,B纔會加入到A的前面。當B的when和A相等時,B只會加入到A的後面。所以,A先被處理。
/frameworks/base/core/java/android/os/Handler.java
718 public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
719 MessageQueue queue = mQueue;
720 if (queue == null) {
721 RuntimeException e = new RuntimeException(
722 this + " sendMessageAtTime() called with no mQueue");
723 Log.w("Looper", e.getMessage(), e);
724 return false;
725 }
726 return enqueueMessage(queue, msg, 0);
727 }
複製代碼
sendMessageAtFrontOfQueue和sendMessage最大的不一樣在於它傳入的uptime爲0。0做爲一種特殊的uptime,它表示將消息加入到隊列頭部。
/frameworks/base/core/java/android/os/MessageQueue.java#enqueueMessage
569 if (p == null || when == 0 || when < p.when) {
570 // New head, wake up the event queue if blocked.
571 msg.next = p;
572 mMessages = msg;
573 needWake = mBlocked;
複製代碼
如上代碼驗證了這一點,當when == 0時,則不論隊列中已有的消息是什麼狀態,新來的消息都會被添加到隊首。所以,若是A已經被加入到隊列中,當再次調用sendMessageAtFrontOfQueue將B加入隊列時,B會搶佔A的隊首位置,所以B先被處理。
/frameworks/base/core/java/android/os/MessageQueue.java
338 synchronized (this) {
......
......
394 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
395 }
396
397 // Run the idle handlers.
398 // We only ever reach this code block during the first iteration.
399 for (int i = 0; i < pendingIdleHandlerCount; i++) {
400 final IdleHandler idler = mPendingIdleHandlers[i];
401 mPendingIdleHandlers[i] = null; // release the reference to the handler
402
403 boolean keep = false;
404 try {
405 keep = idler.queueIdle();
406 } catch (Throwable t) {
407 Log.wtf(TAG, "IdleHandler threw exception", t);
408 }
409
410 if (!keep) {
411 synchronized (this) {
412 mIdleHandlers.remove(idler);
413 }
414 }
415 }
複製代碼
mIdleHandlers是ArrayList<IdleHandler>類型,爲什麼不直接遍歷它來取出其中的IdleHandler對象,而是先將該ArrayList中的元素複製到另外一個數組mPendingIdleHandlers中(394行)呢?
關鍵的緣由在於兩點:
基於以上兩點要求,只能經過複製數組的方式,既保證mIdleHandlers的更新被this同步鎖保護,又將可能的耗時操做從同步塊中挪出。
同步屏障一個主要的做用就是屏蔽隊列中已有的同步消息,使得它們沒法被及時處理。經過這種方式能夠將線程的執行權讓渡給異步消息,從而讓異步消息享受到VIP的待遇。
因爲同步屏障的本質也是一個消息(該消息target爲null,以區別開普通消息),因此只要後續的同步消息添加在它前面,就依然能夠正常獲得處理。
對於一個已經激活的同步屏障而言,將一個新的同步消息添加在它以前最簡單的方法就是sendMessageAtFrontOfQueue。經過該方法能夠將一個同步消息添加到隊首,所以也就得到了被處理的權利。
對於system_server進程而言,其主線程、UiThread和FgThread都會設置slow detection。當線程中消息的delivery time或dispatch time大於閾值時,將會有相應的warning log輸出。
默認dispatch的閾值是100ms,而delivery的閾值是200ms。
Slow dispatch warning:
03-23 14:23:17.533 1505 1505 W Looper : Slow dispatch took 111ms main h=android.app.ActivityThread$H c=android.app.LoadedApk$ServiceDispatcher$RunConnection@67031fa m=0
Slow delivery warning:
03-17 02:36:14.623 914 1243 W Looper : Slow delivery took 648ms android.ui h=com.android.server.policy.PhoneWindowManager 22@7dd9dd5 m=0.
03-17 02:57:06.409 914 1243 W Looper : Drained
Delivery time的warning log有一種特殊形式:Drained。
估計不少人看到這句Log有點不知所云,這實際上是Android爲了減小無效輸出所作的優化。此話怎講?
一旦某個消息的delivery time超過閾值,便意味着兩種可能:
這兩種狀況的本質都是前面的消息對當前消息的影響,所以這種影響具備傳遞性。當一個消息報出slow delivery time的警報時,它後面的消息大機率也會報出這個警報。可是這些警報本質上反映的問題是同一個,因此爲什麼要將重複的信息輸出屢次呢?
爲了減小slow delivery警報重複輸出,Android採用以下代碼進行過濾:
/frameworks/base/core/java/android/os/Looper.java
231 if (slowDeliveryDetected) {
232 if ((dispatchStart - msg.when) <= 10) {
233 Slog.w(TAG, "Drained");
234 slowDeliveryDetected = false;
235 }
236 } else {
237 if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
238 msg)) {
239 // Once we write a slow delivery log, suppress until the queue drains.
240 slowDeliveryDetected = true;
241 }
242 }
複製代碼
當slow delivery的警告輸出一次之後,後面的消息即使超出閾值,也不會再輸出log。只到當某一個消息的delivery time ≤ 10ms時,纔會輸出一句新的log:Drained。
Drained的原意是排幹、耗盡。用在這裏表示本來擁塞的MessageQueue如今已經變得順暢,當前消息的delivery time≤10ms表示此前消息的負面影響已經消散。因此當這句話輸出之後,新一輪的slow delivery檢測又從新開始生效。
另外,因爲delivery time的計算須要message.when的參與,而經過sendMessageAtFrontOfQueue發送的消息其when爲0。因此對於這一類消息,是不會有slow delivery的警報的。