【Android原始碼】Handler 機制原始碼分析

指間沙似流年發表於2017-12-23

為什麼要使用Handler

  1. 因為在Android中訪問UI只能在主執行緒中進行,如果在子執行緒中執行,則程式會丟擲異常。

    // ViewRootImpl.java
    void checkThread() {
       if (mThread != Thread.currentThread()) {
           throw new CalledFromWrongThreadException(
                   "Only the original thread that created a view hierarchy can touch its views.");
       }
    }
    複製程式碼
  2. 為什麼不允許在子執行緒中訪問UI?

    因為Android的UI並不是執行緒安全的,如果在多執行緒中執行UI的操作,那麼UI的狀態是不可控的,這個時候就會出現各種問題。 那麼最好的辦法就是隻能在一個執行緒中執行UI的操作。 而Android主執行緒中又不能執行耗時操作,因為那樣就會導致程式的ANR。 所以Android提供了Handler這樣的機制,用來在子執行緒中執行耗時操作之後,傳送訊息給主執行緒,主執行緒再執行UI的更新操作。

Handler機制原理分析

這裡我們以Android UI執行緒的Handler來分析。

Handler的建立

Android的應用入口是ActivityThread.main()函式,UI執行緒的Handler就是在這個函式中建立的。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    thread.attach(false);
    ActivityThread thread = new ActivityThread();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
}
複製程式碼

當main函式執行之後,應用就啟動了,從這個時候開始,Looper就會一直從訊息佇列中取出訊息並處理。而應用會通過Handler來不斷的新增訊息給訊息佇列。通過不斷的新增訊息和取出訊息,整個訊息機制就被運轉起來了。

Looper

從上面可以看到在Handler建立前後,通過Looper的prepareloop方法,建立了Looper物件和開啟訊息迴圈。

  1. 建構函式

    private Looper(boolean quitAllowed) {
       mQueue = new MessageQueue(quitAllowed);
       mThread = Thread.currentThread();
    }
    複製程式碼
    1. 建立了MessageQueue物件,這個物件是訊息佇列,主要用來存放我們的訊息,會在下面具體分析。
    2. 獲取當前的執行緒並儲存起來
  2. 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));
    }
    複製程式碼
    1. 判斷ThreadLocal中在當前執行緒是否已經存在了Looper物件,如果存在,則丟擲異常,所以可以得出在同一個執行緒中只能存在一個Looper物件。
    2. 在ThreadLocal存入當前執行緒的Looper物件。
    3. ThreadLocal物件是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒儲存資料,當儲存資料之後,就只能在指定的執行緒中獲取到儲存的資料,其他執行緒是不能獲取到資料的。
  3. loop

    public static void loop() {
       final Looper me = myLooper();
       // 獲取訊息佇列
       final MessageQueue queue = me.mQueue;
        // 死迴圈來輪詢的從訊息佇列中獲取訊息
       for (;;) {
           Message msg = queue.next(); // might block
           if (msg == null) {
               return;
           }
           
           try {
               msg.target.dispatchMessage(msg);
           } finally {
               if (traceTag != 0) {
                   Trace.traceEnd(traceTag);
               }
           }
    
           msg.recycleUnchecked();
       }
    }
    複製程式碼
    1. loop函式會不斷的從訊息佇列中取出訊息,當queue.next()為空的時候就直接阻塞住
    2. msg不為空,呼叫msg.target.dispatchMessage(msg)處理訊息,而msg.target其實就是Handler物件,取出訊息之後就交給Handler來處理髮送的訊息了。
  4. quit

    public void quit() {
       mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }
    複製程式碼

    呼叫MessageQueue的quit方法來退出Looper。其中上面的是直接退出,下面的是安全的退出,也就是隻標記一下,當訊息佇列中的訊息全部執行完成之後安全的退出。

    當我們在子執行緒中建立Looper和Handler的時候,如果我們不呼叫quit方法的話,子執行緒就會一直處於等待狀態,所以我們需要呼叫quit方法退出Looper。

MessageQueue

在Looper中大量的使用到了MessageQueue,而MessageQueue主要就是兩個操作插入訊息和讀取訊息並刪除訊息,我們來一起分析下:

  1. 建構函式

    MessageQueue(boolean quitAllowed) {
       mQuitAllowed = quitAllowed;
       mPtr = nativeInit();
    }
    複製程式碼

    在建構函式中,呼叫了nativeInit方法在Native層建立了NativeMessageQueue和Looper,並將Looper設定給當前的執行緒。這個時候Java層和Native層都有了MessageQueue和Looper。 在Native層的Looper中,建立了一個管道,本質上就是一個檔案,一個管道中有讀和寫兩個檔案操作符,通過讀和寫來將訊息寫入和讀取。

  2. enqueueMessage 插入訊息

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
           msg.markInUse();
           msg.when = 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 {
               needWake = 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()) {
                       needWake = false;
                   }
               }
               msg.next = p; // invariant: p == prev.next
               prev.next = msg;
           }
    
           // We can assume mPtr != 0 because mQuitting is false.
           if (needWake) {
               nativeWake(mPtr);
           }
       }
       return true;
    }
    複製程式碼

    enqueueMessage其實就是構建好msg,並插入到單連結串列中。

  3. next 讀取訊息並刪除訊息

    Message next() {
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
        }
    }
    複製程式碼

    next方法是一個無限迴圈方法,當訊息佇列中沒有訊息的時候,next方法會被阻塞在這裡,當有新的訊息的時候,next方法會最終返回這條訊息並從單連結串列中移除。

Handler

  1. 建構函式

    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;
    }
    複製程式碼
    1. 首先獲取Looper物件,如果Looper物件為空,則丟擲異常,所以當我們在子執行緒中建立Handler的時候,首先要通過Looper.prepare()來建立子執行緒的Looper物件。
    2. 通過Looper獲取到MessageQueue
  2. 傳送訊息

    Handler的傳送訊息主要是post的方法和send的方法,而他們最終呼叫的都是:

    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);
    }
    複製程式碼

    Handler主要就是向訊息佇列中插入了一條訊息,這個時候MessageQueue就會呼叫插入的方法來插入訊息。

  3. 讀取訊息

    當Handler傳送訊息之後,MessageQueue就會通過next方法讀取出這個訊息,那麼Looper的loop就不會被阻塞住,取出訊息之後就呼叫了msg.target.dispatchMessage(msg)方法,最終交給Handler的dispatchMessage來執行。

    /**
    * Handle system messages here.
    */
    public void dispatchMessage(Message msg) {
       if (msg.callback != null) {
           handleCallback(msg);
       } else {
           if (mCallback != null) {
               if (mCallback.handleMessage(msg)) {
                   return;
               }
           }
           handleMessage(msg);
       }
    }
    複製程式碼
    1. 檢查Message的callback是否為空,不為空則通過handleCallback來處理,而callback就是通過post傳遞過來的Runnable物件。直接呼叫run方法執行。

      private static void handleCallback(Message message) {
          message.callback.run();
      }
      複製程式碼
    2. 檢查mCallback是否為空,不為空則呼叫mCallback.handleMessage方法處理訊息。而mCallback就是我們在建立Handler的時候傳遞過去的callback。

      public interface Callback {
          public boolean handleMessage(Message msg);
      }
      public Handler(Callback callback) {
          this(callback, false);
      }
      複製程式碼
    3. 呼叫handleMessage(msg)處理訊息。

相關文章