一、為什么要使用 Handler
眾所周知,Android 不允許在子執行緒中更新 UI,但是我們在子執行緒完成耗時的操作之后,需要對界面資料進行更新,又該怎么處理呢?這時候,我們可以使用 Handler 進行 UI 更新,值得注意的是,更新 UI 我們需要把 Message 發送到主執行緒持有的 MessageQueue ,否則程式依然就會發生奔潰,
另外,除了更新 UI,Handler 是 Android 系統的訊息傳遞機制,它定義了一套處理訊息的規則,廣播、服務以及執行緒間的通信都需要靠它來完成,
與 Handler 相關的還有 Looper 和 MessageQueue,接下來我們就從它的使用開始分析,對這三劍客一網打盡,
二、Handler 發送訊息的流程
Handler 發送訊息有兩種方式,一種是 sendMessage 的方式,一種是 post 的方式,通過對原始碼的閱讀,post 的方式其實是呼叫到了 sendMessage 的方式,那我們就來看看 sendMessage 的流程吧,通過呼叫 sendMessage,最侄訓走到下面方法中:

這里做的事情很簡單,必須滿足 MessageQueue 不能為空,否則程式會拋出例外,接下來看 enqueueMessage 的流程:

在這里完成了兩個重要的流程:
- 為 msg 的 target 賦值,msg.target = this,
因此這個 target 就是呼叫的 sendMessage 的 Handler,(記住這里的重點) - 呼叫了 MessageQueue 的 enqueueMessage 方法,
到目前為止,流程來到了 MessageQueue 中,現在看 MessageQueue 的 enqueueMessage 方法,
三、MessageQueue 的作業流程
由于 enqueueMessage 的方法比較長,我們這里不截圖,直接看下面的代碼:(省略部分代碼)
boolean enqueueMessage(Message msg, long when) {
// 1、target 不能為空,否則直接拋出例外
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 2、加鎖,不能有多個 Handler 同時發送訊息
synchronized (this) {
msg.when = when;
Message p = mMessages; // 出佇列的 msg 的下一個要出佇列的 msg
boolean needWake;
// 3、下面這三種情況直接插在 head 節點上,(1)這個佇列是一個空佇列,
// (2)這個 msg 需要立即處理,(3)是它需要處理的時間比即將出佇列的節
// 點的處理時間還要小
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();
// 4、如果之前第三點的條件不滿足,就會從 head 節點開始遍歷,
// 插入到一個合適的時間,或者鏈表的尾部,這個 for 回圈做的其實就是
// 鏈表節點的插入
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;
}
// 5、是否需要進行喚醒,在 queue.next() 方法中如果沒有獲取到 msg就會休眠
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
解釋其實已經在上面的代碼里了,下面來做一個簡單歸納:
MessageQueue 其本質上一個單向鏈表,入佇列這個操作進行了加鎖的處理,不能多個 msg 同時入佇列,- 在插入佇列的時候,會根據當前佇列是否為空,或者處理訊息的時間選擇合適的插入位置,
- 最后判斷是否需要進行 wake up
到目前為止,我們看了 Handler 的發送訊息的流程,以及訊息是如何插入鏈表的,那么訊息是如何處理的呢?我們知道,只有呼叫了 Looper 的 loop() 方法之后,才能處理訊息,那接下來看 Looper 的 loop() 方法,
四、Looper 的作業流程
Looper 的 loop() 方法也是相當長,接下來看代碼:(省略部分代碼)
public static void loop() {
// 1、獲取 Looper 物件,定進行判空處理
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 2、獲取了 MessageQueue 物件
final MessageQueue queue = me.mQueue;
for (;;) {
// 3、呼叫 MessageQueue 的 next(),回傳值是 msg
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
....
try {
// 4、之前說過,在 SendMessage 的時候設定了 msg 的target,這個 target 就是呼叫 sendMessage 的 Handler
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
} finally {
}
msg.recycleUnchecked();
}
}
代碼本身很長,但是其實做的事情也不多,現在簡單歸納一下:
- 在呼叫 Looper.loop() 之前,必須先呼叫 Looper.prepare(),如果沒有
Looper 物件的話程式會直接拋例外, - 通過呼叫 MessageQueue 的 next 方法不斷的從佇列里取訊息出來,
- 最后把 msg 交給 Handler 的 dispatchMessage() 進行處理,
通過原始碼我們可以發現呼叫 queue.next() 時可能發生阻塞,那這個方法又做了什么?還有,為什么要先呼叫 Looper.prepare(),這個方法又做了什么處理?先來看比較簡單的吧:

這個 Looper.prepare() 其實是創建了一個 Looper 物件,并且通過 ThreadLocal 實作每個執行緒有且僅有一個這樣的 Looper 物件,為什么要創建 Looper 呢?沒有就不行嗎?我們來看 Handler 的建構式:

可以看到,如果 Looper 為空的話,程式直接拋例外,這個 myLooper() 是用來獲取當前執行緒的 Looper 物件:

從時序上說,我們呼叫 Looper.prepare() 的時機必須在 new Handler() 之前,那么,我們主執行緒使用 Handler 的時候,并沒有呼叫 Looper.prepare() 這個方法,這又是怎么回事呢?
原來,在 ActivityThread 的 main() 方法中已經為我們進行了處理:

這個 prepareMainLooper() 在內部呼叫了 Looper.prepare() ,到目前為止,我們解決了 Looper 的相關問題,說明了必須存在 Looper 的原因,現在還有一個問題沒有解決,queue.next() 方法做了什么事情?它為什么發生阻塞呢?
接下來看 MessageQueue 的 next() 方法:(已省略部分代碼)
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 1、這是一個 native 方法,如果messageQueue 沒有可以處理的訊息就會休眠
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 2、同步屏障,尋找佇列中的下一個異步訊息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 3、下一個出佇列的這個 msg 還沒有到時間,并計算需要阻塞的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 4、得到一個能夠處理的msg,并回傳這個 msg
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;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
}
}
重要的點其實已經在上面說了,下面總結一下:
- 獲取 msg 的這個程序有可能會發生阻塞,具體呼叫到的是 native 的 nativePollOnce 方法
- 獲取訊息的時候,有一個同步屏障,也就是對 msg 對應的 target(Handler) 為空的訊息進行了過濾,
- 如果能獲取到一個 msg ,那么就回傳這個 msg,
四、再看 Handler
先來梳理一下我們現在明白了什么:
- 在創建 Handler 的時候,必須先創建 Looper 物件,之后還需要呼叫 Looper.loop() 方法才能讓 Handler 開始作業,
- 通過 Handler sendMessage 發送訊息,其實是呼叫了 queue.enqueueMessage,這個 Queue 其實是一個單向鏈表,在呼叫這個方法的時候,會根據當前佇列的轉態以及 when 把這個 msg 插入到合適的位置,
- queue.next() 可能會發生休眠,原因是拿到不到合適的 msg,在 queue.enqueueMessgae 的時候會判斷是否需要喚醒,
之前我們說過,這個 msg 其實是交給了 Handler 的 dispatchMessage 去處理,下面來看一下 Handler 是怎么處理的:

msg.callback是我們通過 post 方法傳遞進來的一個 Runnable 物件,如果我們沒有使用 post 的話,就不會走到handleCallback(msg)中,- mCallback 是一個 CallBack 物件,如果我們在創建 Handler 的時候沒有傳這個引數,那么 mCallback 也是為null 的,
- 最后才會走到 handleMessage(msg) 中,
為方便學習了解到更多的Handler知識點,特此我將一些 Android 開發相關的學習檔案、面試題、Android 核心筆記等檔案進行了整理,并上傳之我GitHub專案中,如有需要參考的可以直接去我 GitHub ,希望能幫助到大家學習提升,



轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/300019.html
標籤:其他
