Android Framework | 消息機制的冷門知識點

做爲Android的基礎知識,消息機制已被無數人寫過。筆者曾經也寫過一篇深刻分析的文章,但整體而言乏善可陳,並沒有新穎之處。最近剛好從新整理了一下思路,想着能夠從細節的角度出發,對一些冷門的知識點作一個概括。記錄於此,供你們批評討論。html

本文全部代碼基於Android Q (10.0)java

1. 哪一個消息在前?哪一個消息在後?

假設線程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

1.1 當咱們調用sendMessage發送消息時

/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先被處理。

1.2 當咱們調用sendMessageAtFrontOfQueue發送消息時

/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先被處理。

2. mIdleHandlers爲何要拷貝數組?

/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行)呢?

關鍵的緣由在於兩點:

  1. mIdleHandlers中成員的更改必需要持有this(MessageQueue對象)同步鎖,不然可能形成線程間的數據競爭狀態。
  2. IdleHandler.queueIdle方法的執行時間是未知的,所以不能在持有this同步鎖的狀態下去執行該方法。若是是持有this同步鎖,且queueIdle方法的執行時間過長,那麼其餘調用Handler.sendMessage的線程將可能由於等鎖而發生阻塞。這實際上是多線程編程中一條基本法則:在同步塊中只作必要的事,少作耗時的事。

基於以上兩點要求,只能經過複製數組的方式,既保證mIdleHandlers的更新被this同步鎖保護,又將可能的耗時操做從同步塊中挪出。

3. 當消息隊列頭部是已激活的同步屏障時,還可以處理同步消息麼?

同步屏障一個主要的做用就是屏蔽隊列中已有的同步消息,使得它們沒法被及時處理。經過這種方式能夠將線程的執行權讓渡給異步消息,從而讓異步消息享受到VIP的待遇。

因爲同步屏障的本質也是一個消息(該消息target爲null,以區別開普通消息),因此只要後續的同步消息添加在它前面,就依然能夠正常獲得處理。

對於一個已經激活的同步屏障而言,將一個新的同步消息添加在它以前最簡單的方法就是sendMessageAtFrontOfQueue。經過該方法能夠將一個同步消息添加到隊首,所以也就得到了被處理的權利。

4. Delivery Time 和 Dispatch Time

  • Delivery Time = Dispatch Start - message.when,表示該消息實際輪詢與理論輪詢的時間差。
  • Dispatch Time = 消息的實際處理時間。

對於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 P o l i c y H a n d l e r c = c o m . a n d r o i d . s e r v e r . p o l i c y . P h o n e W i n d o w M a n a g e r PolicyHandler c=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超過閾值,便意味着兩種可能:

  1. 前面有太多的消息須要處理,雖然每一個消息處理時間都不長,可是雪崩來了,沒有一片雪花是無辜的。
  2. 前面某些消息的處理時間過長。

這兩種狀況的本質都是前面的消息對當前消息的影響,所以這種影響具備傳遞性。當一個消息報出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的警報的。

相關文章
相關標籤/搜索