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的警报的。

相关文章
相关标签/搜索