1. 程式人生 > >Android的訊息機制(Handler的工作原理)

Android的訊息機制(Handler的工作原理)

Android的訊息機制

Android中的訊息機制其實也就是Handler的執行機制。Android中通過使用Handler來進行更新UI的操作。因為Android的UI更新是單執行緒模型,UI控制元件也都是非執行緒安全的。其原因是如果給UI控制元件加鎖,那麼效率將會變得底下的同時還會將UI訪問的邏輯變得複雜。

Handler的執行基於MessageQueueLooper的支撐。MessageQueue顧名思義是訊息佇列。但其實是一個單項鍊表結構來儲存資訊Message的。而Looper則是不斷去讀取訊息佇列MessageQueue中的資訊,將其交與傳送該訊息的HandlerdispatchMessage

進行處理。

下面我們就HandlerMessageQueueLooper分別分析一下其各自的工作原理。

MessageQueue

訊息佇列中維護了一個Message型別的變數mMessagesMessage是連結串列結構,所以之前所說訊息佇列是通過單項鍊表結構來儲存訊息。 當訊息佇列收到來自Handler的訊息時,訊息佇列需要將此條訊息插入佇列中也即mMessages中。訊息插入順序和它的執行之間相關。

boolean enqueueMessage(Message msg, long when){
    // 省略部分程式碼
    ......
    Message p = mMessages;
    boolean needWake;
    if
(p == null || when = 0 || when < p.when){ // 如果佇列中無訊息或者執行時間最近 // New head, wake up the event queue if blocked. msg.next = p; //把訊息插入到訊息佇列的頭部 mMessages = msg; needWake = mBlocked; }else{ // Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWeke = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for(;;){ prev = p; p = p.next; if(p == null || when < p.when){ break; // 如果除第一條訊息外之後無訊息或者之後的執行時間都大於該訊息執行時間就跳出去插入資訊。 } if(needWake && p.isAsynchronous()){ neesWake = false; } } msg.next = p; ///把訊息插入到訊息佇列的頭部 prev.next = msg; } if(neesWake){ nativeWake(mPtr); } } return true; }

主要是進行了訊息的單鏈表插入,現在我們再來看下新的函式讀取函式`next()。

...
for(;;){
  ...
  Message prevMsg = null;
  Message msg = mMessages;
  if (msg != null && msg.target == null) {
  // Stalled by a barrier.  Find the next asynchronous message in the queue.
  do {
     prevMsg = msg;
     msg = msg.next;
   } while (msg != null && !msg.isAsynchronous());
  }
  if (msg != null) {
     if (now < msg.when) {
        // 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);
      } 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;
  }
}

next()方法中有一個無線迴圈,如果沒有訊息,next()會一直阻塞。知道有訊息來,會返回這條訊息並從訊息佇列中移除。

在分析完了訊息佇列的插入和讀取訊息之後,我們應該再看下Looper,但是在分析Looper之前,我們需要先對ThreadLocal有所瞭解。

ThreadLocal

ThreadLocal是一個執行緒內部的資料儲存類。通過它可以在指定的執行緒記憶體儲資料,其他執行緒無法訪問到該執行緒的資料。當作用域是執行緒的時候可以使用ThreadLocal。作用有兩個:1.在指定執行緒儲存資料。2.複雜邏輯下的物件傳遞。

ThreadLocal會從各自執行緒內部取出一個數組,然後再從陣列中根據當前ThreadLocal的索引去查找出對應的Value的值。

public void set(T value){
    Thread currentThread = Thread.currentThread();
    Values valuse = Values(currentThread);
    if(valuse == null){
      initializeValues(currentThread);
    }
    values.put(this, value);
}

可以看到,通過當前執行緒拿到陣列Values,如果沒有,則傳入當前執行緒初始化一個數組。然後將值放到陣列中。我們再看下是如何將值放到陣列中的。

private void set(ThrealLocal<?> key, Object value){
...
 Entry[] tab = table;
 int len = tab.length;
 int i = key.threadLocalHashCode & (len-1);

 for (Entry e = tab[i];
      e != null;
      e = tab[i = nextIndex(i, len)]) {
     ThreadLocal<?> k = e.get();

     if (k == key) {
         e.value = value;
         return;
     }

     if (k == null) {
         replaceStaleEntry(key, value, i);
         return;
     }
 }

可以看到,ThreadLocal的值儲存位置永遠都是ThreadLocal的欄位i(值的hashCode和長度減1的值做位運算)的下一個位置。

get方法則是找出欄位i的值,然後從陣列的i的下一位獲取到ThreadLocal的值。

好了,講解完了ThreadLocal之後,我們需要分析一下Looper的原理了。Looper在訊息機制中扮演訊息迴圈的角色。通過不斷的Loop()去處理訊息佇列中的訊息。我們先來分析Looper的建構函式。

prite Looper(boolean quitAllowed){
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

可以看到,初始化Looper的時候,會內部構建一個訊息佇列。我們可以看到這個建構函式是一個私有函式,那我們應該如何為一個執行緒建立一個Looper呢。我們可以通過Looper.prepare()為執行緒建立一個Looper。然後再通過Looper.loop()來開啟訊息迴圈。我們來看下looper()函式是如何實現的。

    public static void loop() {
      final Looper me = myLooper();
      if (me == null) {
          throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
      }
      final MessageQueue queue = me.mQueue;

      // Make sure the identity of this thread is that of the local process,
      // and keep track of what that identity token actually is.
      Binder.clearCallingIdentity();
      final long ident = Binder.clearCallingIdentity();

      for (;;) {
          Message msg = queue.next(); // might block
          if (msg == null) {
              // No message indicates that the message queue is quitting.
              return;
          }

          // This must be in a local variable, in case a UI event sets the logger
          final Printer logging = me.mLogging;
          if (logging != null) {
              logging.println(">>>>> Dispatching to " + msg.target + " " +
                      msg.callback + ": " + msg.what);
          }

          final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

          final long traceTag = me.mTraceTag;
          if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
              Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
          }
          final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
          final long end;
          try {
              msg.target.dispatchMessage(msg);
              end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
          } finally {
              if (traceTag != 0) {
                  Trace.traceEnd(traceTag);
              }
          }
          if (slowDispatchThresholdMs > 0) {
              final long time = end - start;
              if (time > slowDispatchThresholdMs) {
                  Slog.w(TAG, "Dispatch took " + time + "ms on "
                          + Thread.currentThread().getName() + ", h=" +
                          msg.target + " cb=" + msg.callback + " msg=" + msg.what);
              }
          }

          if (logging != null) {
              logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
          }

          // Make sure that during the course of dispatching the
          // identity of the thread wasn't corrupted.
          final long newIdent = Binder.clearCallingIdentity();
          if (ident != newIdent) {
              Log.wtf(TAG, "Thread identity changed from 0x"
                      + Long.toHexString(ident) + " to 0x"
                      + Long.toHexString(newIdent) + " while dispatching to "
                      + msg.target.getClass().getName() + " "
                      + msg.callback + " what=" + msg.what);
          }

          msg.recycleUnchecked();
      }
  }

我們可以看到loop()中是一個死迴圈,只有當訊息佇列的next()方法返回為null的時候才會退出。當Looperquit方法呼叫的時候,MessageQueuequitquitSafely方法會通知訊息佇列退出,這個時候訊息佇列的next()就會返回為null。而沒有訊息的時候next()會一直阻塞,這樣導致了loop也一直阻塞。當有訊息的時候,Looper則會通過訊息的target找到傳送訊息的handler然後呼叫其dispatchMessage(Message msg)去將訊息給Handler處理。而dispatchMessage是在建立Handler時所使用的Looper中執行的。所以就將邏輯程式碼轉移到了指定的執行緒上執行了。

好了,大致分析了Looper之後,我們再接著對Handler做一個簡單的瞭解。

Handler

Handler的工作主要是傳送和接收訊息,也即sendMessagedispatchMessage兩個函式。 在sendMessage是將訊息佇列中插入一條訊息,即呼叫enqueMessageMessageQueue收到訊息之後呼叫next()將訊息傳給Looper處理,然後Looper又將訊息丟給該Handler來處理。

public void dispatchMessage(Message msg) {
   if (msg.callback != null) {
       handleCallback(msg);
   } else {
       if (mCallback != null) {
           if (mCallback.handleMessage(msg)) {
               return;
           }
       }
       handleMessage(msg);
   }
}

其中callback是一個介面,是用來例項化Handler時候的一個Runnable物件。

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

主執行緒的訊息迴圈

這裡稍微再講解一下主執行緒的訊息迴圈。Android的主執行緒UI執行緒也就是ActivityThread。其main()方法中系統會通過Looper.prepareMainLooper()來建立主執行緒的Looper以及MessageQueue。通過loop()來開啟主執行緒的訊息迴圈。ActivityThread中有一個Handler來進行訊息迴圈,這個Handler就是AcitivytThread內的ActivityThread.HActivityThread通過ApplicationThreadAMS進行程序間通訊,AMS以程序間通訊的方式完成ActivityThread的請求後會回撥ApplicationThreadBinder方法,然後ApplicationThread會向H傳送訊息,H接收到訊息後會將AplicationThread中的邏輯切換到AcitivyThread中執行,也就是切換到主執行緒執行,這個過程就是主執行緒的訊息迴圈模型。

以上摘選自任玉剛的《Android開發藝術探索》加上自己的一丟丟記憶理解。