1. 程式人生 > >Android面向面試複習----Handler詳解

Android面向面試複習----Handler詳解

Handler詳解

這篇文章緣起於一道面試題:
Android面試題 請解釋下單執行緒模型中Message、Handler、MessageQueue、Looper之間的關係
雖然能夠大致說明白,但是自己對答案也不太滿意,翻一翻原始碼,從原始碼角度剖析一番。

1. 概述Handler相關物件模型關係

  1. 首先看一下Handler、Looper、MessageQueue、Message的相關類圖

    Handler中有兩個成員變數:mLooper、mQueue,分別對應的是Looper和MessageQueue的例項。
    Looper中包含一個成員變數mQueue,對應的是MessageQueue的例項。
    在建立Handler的時候會獲取當前執行緒的Looper,並將Looper對應的訊息佇列也賦值給Handler中的mQueue,用來儲存訊息。

    這裡寫圖片描述

  2. Handler的構造方法

    平時我們都是new Handler()建立,最後還是呼叫的兩個引數的構造方法

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
    
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    1. 在構造方法中通過mLooper = Looper.myLooper();建立了Looper。

      1. myLooper方法中是從sThreadLocal中獲取的Looper:sThreadLocal.get()
      2. Looper是在什麼地方建立的呢?答案是prepare中,所以在子執行緒中,沒有呼叫Looper.prepare()不能建立Handler,而主執行緒預設執行了Looper.prepare(),所以不會有這個問題。

        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));
        }
        
      3. sThreadLocal是跟執行緒相關的,所以,每個執行緒可以對應一個Looper及MessageQueue
    2. 通過Looper,獲取到了Looper對應的MessageQueue,並賦值給Handler中的MessageQueue

    3. 這樣就把Handler和Looper、MessageQueue串聯了起來。

2. 從原始碼角度剖析3個流程

  1. 傳送訊息

    傳送訊息有兩種方式:sendMessage(Message msg),或者post(Runnable r),最終都是呼叫到了sendMessageAtTime(Message msg, long uptimeMillis)

         public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }
    
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    其中最關鍵的是queue.enqueueMessage(msg, uptimeMillis)訊息佇列把Message放到了訊息佇列中,儲存起來。

  2. 分發訊息

    Looper呼叫了loop()方法之後,就會不停的從MessageQueue中讀取訊息,只要讀取到訊息,就會進行分發處理。下面我們把loop()中讀取訊息,分發處理邏輯摘出來。

    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 traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
    }
    

    其中:Message msg = queue.next();從訊息佇列中取出訊息
    如果訊息不為空,則使用Handler的dispatchMessage方法進行分發處理。

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

    這裡主要有兩個分支:

    • handleCallback(msg)是處理的Runnable任務
    • handleMessage(msg)是處理的普通訊息任務
  3. 處理訊息

    處理訊息就是執行HandleMessage中的邏輯或者Runnable中的邏輯。

    1. 在看Looper.loop()方法邏輯的時候,最後一行被我刪掉了,這一行是等訊息分發處理完後對msg的回收處理。

      msg.recycleUnchecked();
      
    2. 原始碼如下:

      void recycleUnchecked() {
          // Mark the message as in use while it remains in the recycled object pool.
          // Clear out all other details.
          flags = FLAG_IN_USE;
          what = 0;
          arg1 = 0;
          arg2 = 0;
          obj = null;
          replyTo = null;
          sendingUid = -1;
          when = 0;
          target = null;
          callback = null;
          data = null;
      
          synchronized (sPoolSync) {
              if (sPoolSize < MAX_POOL_SIZE) {
                  next = sPool;
                  sPool = this;
                  sPoolSize++;
              }
          }
      }
      

3. 文章開頭的面試題答案整理

工作流程:

  1. 在單執行緒中,Looper輪詢器被呼叫prepare()和loop()後,它會不斷的從MessageQueue頭部讀取Message;
  2. 建立Handler時,Handler內部會持有當前執行緒對應的Looper和MessageQueue的引用。
  3. 如果該執行緒中有Handler傳送訊息給MessageQueue,Looper就能夠取出該訊息,通過Message的target(Handler)的dispathMessage,進行處理(兩個分支,一個處理Runnable,一個處理普通Message)。
  4. 處理完成後,Looper又繼續進行訊息的拉取,如此迴圈往復。直到呼叫removeCallbacksAndMessages可以將當前Handler中的所有任務給取消掉。