六、Android 訊息機制之Handler
從開發的角度來講,Handler 是 Android 訊息機制的上層介面。因此我們主要討論的是 Handler 的執行機制。
那麼首先回答個問題,為什麼要有 Handler 機制?
0. 為什麼要有 Handler 機制?
回答這個問題,首先我們得知道 Handler 有什麼作用。
作用:Handler 的主要作用是將一個 任務 切換到 Handler 所在的執行緒中 去執行。
而 Android 規定訪問 UI 這個 任務 只能在主執行緒中進行,如果在子執行緒中訪問 UI,就會丟擲異常。每次操作 UI 時會進行驗證,這個驗證是通過 ViewRootImpl 類裡面的 checkThread 方法 完成的。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
只能在主執行緒就只能在主執行緒訪問唄,那為什麼還要這個 Handler 呢,因為 Android 又建議在主執行緒中不能進行耗時操作,否則會導致 ANR 。那考慮這樣一種情況,比如我們要從服務端拉取一些資料並將其顯示在 UI 上,這個必須在子執行緒中進行拉取動作,拉取完畢後又不能在子執行緒中訪問 UI,那我該如何將訪問 UI 的工作切換到主執行緒中呢?對,就是 Handler。
因此係統之所以提供 Handler,主要原因就是為了 解決在子執行緒中無法訪問 UI 的矛盾。
Q 為什麼不允許在子執行緒中訪問 UI?
因為 Android 的 UI 控制元件 不是執行緒安全的 ,如果在 多執行緒中併發訪問 可能會導致 UI 控制元件處於不可預期的狀態。
Q 那為什麼不對 UI 控制元件的訪問加上 鎖機制呢?
- 首先加鎖會讓 UI 訪問的邏輯變得複雜
- 其次鎖機制會降低 UI 訪問的效率,因為鎖機制會阻塞某些執行緒的執行。
1. Looper、MessageQueue、Message、Handler
訊息機制的模型主要由以上四個類組成:
- Message: 訊息 分為 硬體產生的訊息(如按鈕、觸控等) 和 軟體生成的訊息 。
- MessageQueue: 訊息佇列 ,內部採用 單鏈表 的資料結構來儲存 訊息列表 。
- Handler: 訊息處理者,主要用於向 訊息佇列 傳送 各種訊息事件 和 處理 相應訊息事件。
- Looper: 訊息迴圈,不斷 迴圈 執行(Looper.loop),用於從 訊息佇列中取出 訊息 ,並按 分發機制 將取出的訊息分發給 訊息處理者。
2. 示意圖
下圖完整類圖取自 ofollow,noindex">http://gityuan.com/2015/12/26/handler-message-framework/

訊息機制Main.jpg
下圖為簡化版本:

訊息機制.png
針對簡化版做個解釋:
-
1、整個訊息機制從 Looper 開始。我們都知道使用Handler 時,如果沒有 Looper 會丟擲異常, 這個在原始碼中很清楚。所以整個訊息機制這個大機器啟動的源頭就是 Looper, 通過呼叫 Looper.prepare() 我們會 new 一個 Looper 物件,並存放在 ThreadLocal 裡,這個 ThreadLocal 稍後解釋。而在 Looper 的建構函式中,會 new MessageQueue 物件。
-
2、呼叫 Looper.loop() 啟動整個訊息機制。 在呼叫 loop 方法後, Looper 就開始了自己的 無限迴圈之路, 會一直從 MessageQueue 中取 Message, 這個操作對應的程式碼是 loop 方法裡的 queue.next(), MessageQueue 中的 next 也是一個 無限迴圈,如果 MessageQueue 中沒有訊息, 那麼 next 方法就會阻塞在這裡,相應的 Looper 也會阻塞。 當有新訊息到來時, 會喚醒他們。 至此,整個訊息機制已經運轉起來了,就等 訊息 發過來了。
-
3、Handler 傳送訊息。 首先我們得 new 一個 handler 才能傳送訊息,在我們 new handler 的時候,會將當前執行緒的 Looper 取出來,同時 得到 Looper 裡的 MessageQueue。 有了訊息佇列,我們就能往佇列裡插入資料了。 handler 的訊息傳送最終都是呼叫的 MessageQueue.enqueueMessage() 方法。 這樣我們就把一個 Message 傳送到了 MessageQueue 裡。噢,對了,Message 是自己構建的,這個就不說了。
-
4、此時 訊息佇列裡有了 Message,那麼 MessageQueue 的next 就會把剛剛那個 訊息返回給Looper, Looper 收到 Message 後, 會開始 訊息的分發,就是呼叫 Message.target.dispatchMessage(msg), 這個 Message 就是剛剛傳送的 Message,那 target 就是 Message 裡持有的 Handler 物件。因此 這個訊息的處理 又回到了 Handler 這裡。 那麼 Message 是什麼時候持有的 Handler 物件呢,沒錯,就是在 Handler 傳送訊息時,即呼叫 enqueueMessage 的時候。這個方法內部 第一行程式碼就是
msg.target = this;
,這樣就把 Handler 賦給了 Message 的 target 變數。
上述是整個 訊息機制的 大致流程,嗯,這麼長估計沒人看,我自己都不想看。
看過這麼一大段後,對於 Handler 的主要作用 想必還是一頭霧水。 還記得 Handler 的主要作用嗎?
Handler主要作用:將一個任務切換到 Handler 所在的執行緒中去執行。
瞭解流程後,我知道了一個 訊息 怎麼發給訊息佇列, 然後 Handler 會自己處理這個訊息。但是這個執行緒是怎麼切換的,完全不知道。
上述簡化圖中有個灰色的塊塊, ThreadLocal 。 對,它就是關鍵。
3. ThreadLocal 工作原理
ThreadLocal 是一個執行緒內部的資料儲存類。 還是自己去找定義看吧,這裡就不寫了,我們還是關心它有什麼用。
它的主要作用就是: 可以在不同的執行緒中維護一套資料的副本並且彼此互不干擾。
這又是什麼意思,別慌,這裡有個例子可以方便理解。
public class ThreadLocalSample { private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>(); public void test() { //1、在主執行緒中設定 mThreadLocal 的值為 true mThreadLocal.set(true); System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get()); new Thread("Thread#1") { @Override public void run() { //1、在子執行緒1中設定 mThreadLocal 的值為 false mThreadLocal.set(false); System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get()); } }.start(); new Thread("Thread#2") { @Override public void run() { //1、在子執行緒2中 不設定 mThreadLocal,那麼get得到的值應該為 null System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get()); } }.start(); } public static void main(String[] args) { new ThreadLocalSample().test(); } }
這段程式碼執行的結果如下:

threadlocal_code.png
從結果可知,雖然在不同的執行緒訪問的是同一個 ThreadLocal 物件,但是他們通過 ThreadLocal 獲取到的值卻是不一樣的。
這是為什麼呢?
3.1 ThreadLocal.Values
ThreadLoacal 內部有個 靜態內部類 Values,Values 內部維護的是一個 Object [ ] ,當我們通過 ThreadLoacal 進行 set() 方法呼叫時,實際是在 Values.put 。 當然不同版本的api,實現不一樣,比如最新版本把Values改為了ThreadLocalMap,並且內部維護的是 Entry [ ] 陣列。但是原理都一樣,這裡就 Values 分析簡單點。
/** * Sets the value of this variable for the current thread. If set to * {@code null}, the value will be set to null and the underlying entry will * still be present. * * @param value the new value of the variable for the caller thread. */ public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }
可以看到,當我們呼叫 set 方法時, 會先取得當前的 Thread, 然後把 值 put 進去。 那麼中間那句 Values values = values(currentThread);
是怎麼回事呢。 看 Thread 原始碼。
/** * Normal thread local values. */ ThreadLocal.Values localValues;
你可以看到 Thread 的成員變數裡持有 ThreadLocal.Values 。 所以當我們 set 時,會先從當前執行緒那裡獲取到 Values 物件, 也就是說我們實際是在給 每個執行緒的 Values 賦值。那麼 values(currentThread) 做了什麼呢。
/** * Gets Values instance for this thread and variable type. */ Values values(Thread current) { return current.localValues; }
很簡單,就是返回 執行緒的 localValues 變數。 那麼當我們第一次 set 時, 這個 Values 肯定為空, 那麼就會呼叫 values = initializeValues(currentThread);
來進行初始化。
那麼 ThreadLocal 類裡的 initializeValues 又做了什麼呢。
/** * Creates Values instance for this thread and variable type. */ Values initializeValues(Thread current) { return current.localValues = new Values(); }
沒錯,直接 new Values(),並且把它賦給 Thread 類的 localValues 變數。 這樣當前執行緒就擁有了 ThreadLocal.Values ,當下次set時,就會從這個執行緒中取出這個 Values 並對它進行賦值。 每個執行緒的 Values 是不一樣的。 那 get 就不用說了,也是從當前執行緒中取出這個 Values ,然後獲取相應的值,具體可自行檢視原始碼。
到這裡 ThreadLocal 就分析的差不多了,想必有了個大概印象,針對上述例子給個圖理解。

ThreadLocalValues.png
4. Looper 中的ThreadLocal
分析過 ThreadLocal 後,看 Looper 就不一樣了。
再來看一看 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)); }
sThreadLocal 就是 ThreadLocal 的引用,如下
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
這段我們說過,會new 一個 looper, 同時會把這個 looper 賦值給 ThreadLocal,我們知道 ThreadLocal 呼叫 set 方法的時候,會先獲取當前執行緒,然後呼叫 Values.put , 那麼這個時候,我們就把 主執行緒 與 Looper 繫結在一起了。
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; }
我們可以看到首先會獲取 Looper 物件, 而 Looper.myLooper() 的實現很簡單,就一句話 return sThreadLocal.get();
這樣,如果你是在主執行緒中 new 的 handler, 那麼你也就會在 主執行緒中 處理訊息了。

Handler工作過程.png
5. 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); } }
message.callback.run();
public interface Callback { public boolean handleMessage(Message msg); }
- handleMessage, 當我們自己 new handle 時會重寫這個方法,用來處理 message, 這個方法在 Handler 裡是空實現.
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }