從開發的角度來講,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. 示意圖
下圖完整類圖取自 http://gityuan.com/2015/12/26/handler-message-framework/
下圖為簡化版本:
針對簡化版做個解釋:
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 對象,但是他們通過 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 就分析的差不多了,想必有了個大概印象,針對上述例子給個圖理解。
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, 那么你也就會在 主線程中 處理消息了。
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 是個 Runnable, 如果不為空的話,就會調用 handleCallback , 這里面也就一句話
message.callback.run();
- mCallback 是 Handler類 的一個內部接口
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) {
}