1. 程式人生 > >【Android】從原始碼角度看Handler機制

【Android】從原始碼角度看Handler機制

在Android開發規範中,規定了主執行緒的任務的響應時間不能超過5s,否則會出現ANR,即程式無響應。為了避免這個問題的出現,常用的一個解決方案就是開闢新執行緒,在開闢出來的子執行緒中去處理耗時的業務,然後回到UI執行緒(主執行緒)來重新整理UI,這個過程中“回到UI執行緒重新整理UI”這一步的實現,常用到handler。因此在我的理解中,Handler、主執行緒、子執行緒之間的關係可以比喻成人操作無人機,主執行緒就是操作無人機的人,子執行緒就是無人機,而handler就是人和無人機之間的遙控器,人放出無人機去拍攝各種場景(處理耗時操作),這個過程中人(主執行緒)可以不受影響的做自己的事情,當無人機(子執行緒)處理完任務,會把資料通過遙控器(Handler)的畫面傳遞給人(主執行緒),人收到畫面後對畫面資料進行讀取和使用(更新主執行緒)。例子僅做輔助理解用,如果有理解偏差不到位,還望指出!感謝!
實際上網上有關多執行緒、併發、Handler的資料有很多,多數講解的內容也十分深刻詳細,筆者自知能力有限,本文就不再像各位大佬那樣介紹對Handler的理解了,但是想從原始碼的角度,來分析一下Handler的工作原理,也作為自己學習的內化成果檢測。
首先需要介紹幾個概念:

  • MessageQueue: 訊息佇列,其實這裡說佇列有點不合適,因為實際上其內部儲存並非佇列的形式,而是用了一個單鏈表的資料結構來儲存一系列訊息。
  • Looper: 訊息輪詢器,它能夠不斷的訪問我們的訊息佇列,從而提取其中的訊息以供處理。實現的核心在於其loop()方法。
  • Handler: 既能接收訊息,又能處理訊息,主執行緒和子執行緒的橋樑。
  • Message: 訊息,是資料的載體。

下面我們正向的去了解一下Handler的執行原理:

我們前面說過,Handler相當於一個主執行緒與子執行緒之間的橋樑,這個橋樑之所以能在兩端之間建立聯絡,是因為looper的存在:
我們的 Android 應用在啟動時,會執行到 ActivityThread 類的 main 方法,就和我們以前寫的 java 控制檯程式一樣,其實 ActivityThread 的 main 方法就是一個應用啟動的入口。在這個入口裡,會做很多初始化的操作。其中就有 Looper 相關的設定,程式碼如下

public static void main(String[] args) {

    //............. 無關程式碼...............

    Looper.prepareMainLooper();

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

進入prepareMainLooper()的實現:

public static void prepareMainLooper() {
    prepare(false);//注意這裡
synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }

進來就先執行了一個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));
}

這裡我們可以看到,有一個sThreadLocal變數,如果這個變數非空,則執行prepare會報出異常:Only one Looper may be created per thread,如果這個變數為空,我們會為這個變數賦值一個新的Looper物件。
這裡的Looper(quiteAllowed)顯然是呼叫了Looper的構造方法:

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

至此我們可以發現,整個過程在app的啟動之初,主執行緒開始呼叫Looper.prepareMainLooper()方法, 為sThreadLocal設定了一個唯一的Looper,並且這個Looper是持有當前執行緒的引用的,也就是說,這個Looper與我們呼叫prepareMainLooper的執行緒是一個繫結的關係,而這個執行緒,就是主執行緒。

因此,在app啟動之初,就將主執行緒所對應的Looper物件放進了sThreadLocal中。

說到這裡還需要說一下這個ThreadLocal,ThreadLocal並不是執行緒,它的作用是可以在每個執行緒中儲存資料。它其實類似於一個HashMap,每個執行緒對應了一系列資料。我們通過set和get方法讀取某個執行緒所對應的一些資料的值。這個後文會有例子。

回過頭繼續看剛剛的main入口:

public static void main(String[] args) {

    //............. 無關程式碼...............

    Looper.prepareMainLooper();

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

在為當前執行緒建立好looper並放進sThreadLocal後,就開始執行Looper.loop()方法。這個方法具體是幹什麼的呢?

public static void loop() {
    //獲得一個 Looper 物件
    final Looper me = myLooper();
    // 拿到 looper 對應的 mQueue 物件
    final MessageQueue queue = me.mQueue;
    //死迴圈監聽(如果沒有訊息變化,他不會工作的) 不斷輪訓 queue 中的 Message
    for (;;) {
        // 通過 queue 的 next 方法拿到一個 Message
        Message msg = queue.next(); // might block
        //空判斷
        if (msg == null)return;
        //訊息分發   
        msg.target.dispatchMessage(msg);
        //回收操作  
        msg.recycleUnchecked();
    }
}

這裡就是訊息輪詢的關鍵了,在上文中給出的Looper的構造方法中,我們注意到它建立了一個MessageQueue物件mQueue,未來所有的任務都會在這個mQueue佇列中存放,主執行緒通過loop()去遍歷這個佇列,並依次執行其中的任務。

這裡介紹完looper的初始化過程以及系統通過looper實現的訊息輪詢,下面說一說Handler與這些東西到底有什麼關係,先看Handler的構造方法:

public Handler(Callback callback, boolean async) {
    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;
}
//別忘了myLooper的實現
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

這樣一看,似乎可以明白了——我們每次建立一個Handler,就會呼叫myLooper去從sThreadLocal中拿到當前建立Handler的執行緒的Looper物件,並且根據這個looper物件得到對應的訊息佇列。也就是說,如果我們在主執行緒中建立一個handler,那麼他就會拿到主執行緒的Looper,然後得到主執行緒不斷輪詢的那個訊息佇列。

需要注意的是:執行緒預設是不具有Looper的,如果需要使用Handler就必須為執行緒建立Looper。我們經常提到的主執行緒,也叫UI執行緒,它就是ActivityThread,ActivityThread被建立時就會初始化Looper,這也是在主執行緒中預設可以使用Handler的原因。

當我們使用Handler去傳送訊息的時候,最終都會走進一個函式:

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;//注意這句 檢視定義可以發現 Message的target是一個型別為Handler的成員變數
      //handler遵循誰傳送訊息 誰處理訊息的原則 所以這裡把target設定成self

      // 使用預設的 handler 構造方法時,mAsynchronous 為 false。
      if (mAsynchronous) {
          msg.setAsynchronous(true);
      }
      return queue.enqueueMessage(msg, uptimeMillis);//這句以後,messageQueue就會多一條message
      //looper在呼叫loop()不斷輪訓messageQueue的時候就會發現它,並且處理它
    }

看到這裡相信能夠明白了,我們每次使用handler去傳送訊息後,都會在當前執行緒的訊息佇列中新增一條訊息,當前執行緒輪詢到這條訊息並執行它。
執行它的過程,剛剛我們在loop()方法的原始碼中可以看到,有這樣一個函式:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//我們沒有給上面兩個callback賦值,所以走進這個分支
    }
}


//看這個方法的實現:
public void handleMessage(Message msg) {
//這是一個空方法
}

handleMessage方法的方法體是空的,這也是為什麼我們在使用的時候常常需要複寫一個handleMessage的原因。

至此應該有許多讀者已經看暈了,說實話筆者能力有限…這一塊正向的去解釋確實有點凌亂。因此在這裡再總結一下:

在android中執行緒是預設沒有Looper的,在程式啟動之初,會為主執行緒建立一個Looper物件,並且根據Looper初始化了當前執行緒的訊息佇列,然後開始主動輪詢這個佇列中的訊息。當我們在某個執行緒中建立Handler時,就會獲取當前執行緒的Looper(注意,這個執行緒是建立Handler的執行緒,而不是handler傳送訊息的執行緒),根據looper拿到這個執行緒的訊息佇列,然後未來所有通過Handler傳送的訊息,都會被加入到這個執行緒的佇列中。我們常在主執行緒中建立了Handler後,用一個新的執行緒去呼叫handler.sendmessage()方法,而handler又是遵循自己傳送自己處理的機制,會自己接收到訊息並走入handleMessage方法,我們再通過複寫handleMessage來重新整理UI。

以上是對Handler機制以及其執行過程正向的分析,功力有限可能難以做到深入淺出。

有理解的不對的還望指正!感謝閱讀!