Android Handler機制之Handler 、MessageQueue 、Looper

AndyJennifer發表於2018-09-22

很隨意.jpg

該文章屬於《Android Handler機制之》系列文章,如果想了解更多,請點選 《Android Handler機制之總目錄》

前言

上篇文章,我們講了ThreadLocal,瞭解了執行緒本地變數的實質,如果有小夥伴還是不熟悉ThreadLocal原理的,請參看上篇文章《 Android Handler機制之ThreadLocal》。如果你已經閱讀 了該文章,那現在我們就一起來了解Handler與MessageQueue與Looper三者之間的關係及其內部原理。

Handler、MessageQueue、Looper三者之間的關係

在瞭解其三者關係之前,我先給大家一個全域性的關係圖,接下來的文章會根據該關係圖,進行相應的補充與描述。

HandlerLooperMessage關係.png

從上圖中我們可以看出幾點

  • Handler的建立是與Looper建立的執行緒是相同的。
  • Looper中內部維護了一個MessageQueue(也就是訊息佇列)。且該佇列是通過連結串列的形式實現的。
  • Hanlder最終通過sendMessage方法將訊息傳送到Looper中對應的MessageQueue中。
  • Looper通過訊息迴圈獲取訊息後,會呼叫對應的訊息中的target(target對應的是發訊息的Handler)的dispatchMessage()方法來處理訊息。

Looper原理

因為訊息佇列(MessageQueue的建立是在Looper中內部建立的,同時Handler訊息的傳送與處理都是圍繞著Looper來進行的,所以我們首先來講Looper。

Looper是如何與主執行緒關聯的

在平時開發中,我們使用Handler主要是為了在主執行緒中去更新UI,那麼Looper是如何與主執行緒進行關聯的呢?在Android中,App程式是由Zygote fork 而建立的,而我們的ActivityThread就是執行在該程式下的主執行緒中,那麼在ActivityThread的main方法中,Looper會通過prepareMainLooper()來建立內部的訊息佇列(MessageQueue),同時會通過loop()構建訊息迴圈。具體程式碼如下圖所示:

   public static void main(String[] args) {
		...省略部分程式碼
        Looper.prepareMainLooper();//
        
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製程式碼

要了解當前Loooper如何與主執行緒進行關聯的,需要繼續檢視prepareMainLooper()方法。下述程式碼中,為了大家方便,我將prepareMainLooper()方法所涉及到的方法全部羅列了出來。

   //建立主執行緒Looper物件
   public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
  //Looper與當前主執行緒繫結
  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));//建立Looper物件,放入主執行緒區域性變數中
    }
    
  //獲取當前主執行緒的Looper物件
  public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
   }

複製程式碼

觀察上訴程式碼,我們發現,prepareMainLooper方法內部呼叫prepare()方法(這裡我們忽略該方法中的引數 quitAllowed),而prepare內部呼叫的是ThreadLocal的set()方法。如果你閱讀了之前我寫的《 Android Handler機制之ThreadLocal》,那麼大家應該知道了當前Looper物件已經與主執行緒關聯了(也可以說,當前主執行緒中儲存了當前Looper物件的引用)。

Looper內部建立訊息佇列

在瞭解了Looper物件怎麼與當前執行緒關聯的後,我們來看看Looper類中的具體方法。之前我們說過,在建立Looper物件的時候,當前Looper物件內部也會建立與之關聯的訊息佇列(MessageQueue)。那麼檢視Looper對應的建構函式:

    final MessageQueue mQueue;
	//Looper內部會建立MessageQueue物件
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
複製程式碼

從Looper物件的建構函式中,我們很明顯的看出內部建立了MessageQueue物件,也驗證了我們之前的說法。

Looper的訊息迴圈

當前Looper物件與主執行緒關聯後,接著會呼叫Looper物件中的loop()方法來開啟訊息迴圈。具體程式碼如下圖所示:

    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;
		...省略部分程式碼
        for (;;) {//一直迴圈去獲取訊息佇列中的訊息
            Message msg = queue.next(); //該方法可能堵塞,
            if (msg == null) {
	            //如果沒有訊息,表示當前訊息佇列已經退出
                return;
            }

        ...省略部分程式碼
            try {
             //獲取訊息後,執行傳送訊息的handler的dispatchMessage方法。
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
        ...省略部分程式碼
            }

            msg.recycleUnchecked();
        }
    }
複製程式碼

通過上述程式碼,我們可以看出,在Looper中的loop()方法中會一直去拿當前訊息佇列中的訊息,如果能取出訊息會呼叫該訊息的target去執行dispatchMessage()方法。如果沒有訊息,就直接退出訊息迴圈。

MessageQueue原理

MessageQueue的next()方法

因為Looper中loop()方法會迴圈呼叫MessageQueue中的next方法,接下來帶著大家一起檢視該方法。程式碼如下圖所示:

 Message next() {
	...省略部分程式碼
	 for (;;) {
	     synchronized (this) {
	     ...省略部分程式碼
         if (msg != null) {
           if (now < msg.when) {
                nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
	                    //遍歷訊息列表,取出訊息
                        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;
                    }
                }
              ...省略部分程式碼
        }
      }
    }
複製程式碼

上述程式碼中,我省略了很多程式碼,現在大家不需要關心省略的內容,大家只要關心大的一個方向就夠了,關於MessageQueue的next()具體詳解,會在下篇文章《Android Handler機制之Message的傳送與取出》具體介紹。好了,大家把狀態調整過來。 在上文中,我們說過MessageQueue是以連結串列的形式來儲存訊息的,從next()方法中我們能分析出來,next()方法會一直從MessageQueue中去獲取訊息,直到獲取訊息後才會退出。

MessageQueue的enqueueMessage()方法

通過上文,我們已經瞭解Message取訊息的流程,現在我們來看看訊息佇列的加入過程。

boolean enqueueMessage(Message msg, long when) {
	      ...省略部分程式碼
        synchronized (this) {
          ...省略部分程式碼
            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 {
              ...省略部分程式碼
                //迴圈遍歷訊息佇列,把當前進入的訊息放入合適的位置(比較等待時間)
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //將訊息插入合適的位置
                msg.next = p; 
                prev.next = msg;
            }

	      ...省略部分程式碼
        }
        return true;
    }
複製程式碼

上訴程式碼中,我們把重心放在for迴圈中,在for迴圈中主要乾了 一件事,就是根據當前meesag.when的值,來確定當前插入的訊息應該放入訊息佇列的位置。(當前小夥伴肯能會對message.when感到困惑,還是那句話,現階段我們只用關心主要的流程,具體的方法詳解會在下篇文章《Android Handler機制之Message的傳送與取出》具體介紹)

Handler的原理

瞭解了Looper與MessageQueue的原理後,我們大致瞭解了整個訊息處理的關係,現在就剩下發訊息與處理訊息的流程了。最後一點了,大家堅持看完。

Handler是怎麼與Looper進行關聯的

在文章最開始的圖中,Handler發訊息最終會傳送到對應的Looper下的MessageQueue中。那麼也就是說Handler與Looper之間必然有關聯。那麼它是怎麼與Looper進行關聯的呢?檢視Handler的建構函式:

  //不帶Looper的建構函式
  public Handler() {this(null, false);}
  public Handler(boolean async) {this(null, async);}
  public Handler(Callback callback) {this(callback, false);}
  public Handler(boolean async) {this(null, async);}
  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());
            }
        }
		//Looper.myLooper()內部會呼叫sThreadLocal.get(),獲取執行緒中儲存的looper區域性變數
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
	    //獲取當前Looper中的MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
 //帶Looper引數的建構函式
  public Handler(Looper looper) { this(looper, null, false); }
  public Handler(Looper looper, Callback callback) { this(looper, callback, false);}
  public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        //獲取當前Looper中的MessageQueue
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }  
複製程式碼

在Handler的建構函式中,主要分為兩種型別的建構函式,一種是帶Looper引數的建構函式,一種是不帶Looper引數的建構函式。

  • 在不帶Looper引數的建構函式中,是通過Looper.myLooper()來獲取當前Looper物件的(也就是說,Handler獲取的Looper物件是與當前例項化當前Handler的執行緒相關的,那麼如果Handler物件是在主執行緒中建立的,那麼獲取的就是主執行緒的Looper,注意前提條件當前執行緒執行緒已經通過Looper.prepare()與Looper.loop()構建了迴圈訊息佇列,因為只有呼叫了該方法後,才會將當前Looper物件放入執行緒的區域性變數中
  • 在帶Looper引數的建構函式中,Looper物件是通過外部直接傳入的。(這裡其實給我們提供了一個思路,也就是我們可以構建自己的訊息處理迴圈,具體細節參看類HandlerThread)

Handler怎麼將訊息傳送到MessaageQueue(訊息佇列)中去

在瞭解Handler怎麼將訊息傳送到MessageQueue(訊息佇列),我們先來了解Handler的發訊息的系列方法。

//傳送及時訊息
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean post(Runnable r)

//傳送延時訊息
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean postDelayed(Runnable r, long delayMillis)

//傳送定時訊息
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean postAtTime(Runnable r, long uptimeMillis)
複製程式碼

在Handler發訊息的方法中,我們可以總共發訊息的種類,分為三種情況,第一種是及時訊息,第二種是傳送延時訊息,第三種是定時訊息。其中關於訊息怎麼在訊息佇列中排列與處理。具體的方法詳解會在下篇文章《Android Handler機制之Message的傳送與取出》具體介紹。

通過檢視Handler傳送訊息的幾個方法。我們發現內部都呼叫了MessageQueue的enqueueMessage()方法。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;//設定message.target為當前Handler物件
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);//獲取當前MessageQueue.將訊息加入佇列中
    }
複製程式碼

該方法內部其實很簡單,就是獲取當前MessageQueue物件,將訊息將入訊息佇列中去了。其中需要大家需要的注意的是這段程式碼msg.target = this。該程式碼意思就是當前的訊息儲存著當前傳送訊息的Handler物件的應用。該行程式碼非常重要。因為最後涉及到訊息的處理。

Handler怎麼處理訊息

通過上文的描述,現在我們已經大致瞭解Handler是怎麼將訊息加入到訊息佇列中去了,現在需要關心的是當前訊息是怎麼被處理的。大家還記的之前我們講過的Looper原理吧,Looper會呼叫loop()方法迴圈的取訊息。當取出訊息後會呼叫message.target.dispatchMessage(Message msg)方法。其中message.target從上文我們已經知道了,就是當前傳送訊息的Handler。那麼最終也就會回到Handler中的dispatchMessage()方法。

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {//第一步,判斷msg.callback
            handleCallback(msg);
        } else {
            if (mCallback != null) {//第二步、判斷Handler的callBack
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);//第三步,執行Handler的handleMessage方法
        }
    }
複製程式碼

觀察該方法,我們可以得出,Handler中的dispatchMessage()方法主要處理了三個步驟,下面分別對這三個步驟進行講解

第一步,執行message.callback

在Handler中的dispatchMessage()方法中,我們已經知道如果msg.callback != null,那麼我們會直接走handleCallback(msg)方法。在瞭解該方法之前,首先我們要知道msg.callback對於的類是什麼。這裡我就直接給大家列出來了。其實msg.callback對應是以下四個方法的Runnable物件。

public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)
複製程式碼

以上四個方法在傳送Runnable物件時,都會呼叫getPostMessage(Runnable r) 方法,且該方法都會將Runnable封裝在Message物件的callback屬性上。具體如下getPostMessage(Runnable r) 方法所示:

  private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
複製程式碼

在瞭解了Message的callback到底什麼過後,我們再來看看handleCallback(Message message)方法

 private static void handleCallback(Message message) {
        message.callback.run();
    }
複製程式碼

該方法其實很簡單,就是呼叫相應Runnable的run()方法。

第二步,執行Handler的callBack

如果當前Message.callback為空,接下來會判斷Handler中的Callback回撥是否為空,如果不為空則執行Callback的handleMessage(Message msg)方法。Callback的具體宣告如下:

  //避免建立Handler物件重新HandlerMessage方法,你可以直接傳入Callback介面實現
  public interface Callback {
        public boolean handleMessage(Message msg);
    }
複製程式碼

其中在Handler的幾個建構函式中,可以傳入相應Callback介面實現。

public Handler(Callback callback) 
public Handler(Looper looper, Callback callback) 
public Handler(Callback callback, boolean async)
public Handler(Looper looper, Callback callback, boolean async)
複製程式碼

第三步,執行Handler的handleMessage)

如果都不滿足上面描述的第一、第二情況時,會最終呼叫Handler的handleMessage(Message msg)方法。

  //Handler內部該方法是空實現,需要子類具體實現
  public void handleMessage(Message msg) {  }
複製程式碼

為了方便大家記憶,我將Handler中的dispatchMessage()具體的邏輯流程畫了出來。大家按需觀看。

dispatchMessage步驟.png

最後

看到最後大家已經發現該篇文章主要著重於將Handler機制的整個流程,對於很多的程式碼細節並沒有過多的描述,特別是關於Looper從MessageQueue(訊息佇列)中取訊息與MessageQueue(訊息佇列)怎麼放入訊息的具體細節。不用擔心,關於這兩個知識點將會在下篇文章《Android Handler機制之Message的傳送與取出》具體描述。

相關文章