要點
難點:
- MQ 的
next()
方法,enqueueMessage()
方法,因為它們與 Native 層的 Looper 和 MQ 關聯。
重點:
- 訊息如何分發
- next() 方法
- 如何退出
- Handler 與執行緒對應起來的原理
題目
- Handler 實現機制(很多細節需要關注:如執行緒如何建立和退出訊息迴圈等等)
- 關於 Handler,在任何地方 new Handler 都是什麼執行緒下?
- Handler 發訊息給子執行緒,looper 怎麼啟動?
- 在子執行緒中建立 Handler 報錯是為什麼?
- 如何在子執行緒建立 Looper?
- 為什麼通過 Handler 能實現執行緒的切換?
Handler 機制中有 4 個主要的物件:Handler、Message、MessageQueue 和 Looper. Handler 負責訊息的傳送和處理;Message 是訊息物件,類似於連結串列的一個結點;MessageQueue 是訊息佇列,用於存放訊息物件的資料結構;Looper 是訊息佇列的處理者(用於輪詢訊息佇列的訊息物件,取出後回撥 handler 的 dispatchMessage()
進行訊息的分發,dispatchMessage()
方法會回撥 handleMessage()
方法把訊息傳入,由 Handler 的實現類來處理。)
當我們在某個執行緒當中呼叫 new Handler()
的時候會使用當前執行緒的 Looper 建立 Handler. 當前執行緒的 Looper 存在於執行緒區域性變數 ThreadLocal 中。在使用 Handler 之前我們需要先呼叫 Looper.prepare()
方法例項化當前執行緒的 Looper,並將其放置到當前執行緒的執行緒區域性變數中(只放一次,以後會先從 TL 中獲取再使用,此時會呼叫 Looper 的構造方法,並在構造方法中初始化 MQ),然後呼叫 Looper.loop()
開啟訊息迴圈。主執行緒也是一樣,只是主執行緒的 Looper 在 ActivityThread 的 main()
方法中被例項化。我們可以使用 Looper.getMainLooper()
方法來獲取主執行緒的 Looper,並使用它來建立 Handler,這樣我們就可以在任何執行緒中向主執行緒傳送訊息了。
Looper.prepare(); // 內部會呼叫 Looper 的 new 方法例項化 Looper 並將其放進 TL
new Handler().post(() -> /* do something */);
Looper.loop();
複製程式碼
當例項化 Looper 的時候會同時例項化一個 MessageQueue,而 MessageQueue 同時又會呼叫 Native 層的方法在 Native 層例項化一個 MessageQueue 還有 Looper. Java 層的 Looper 和 Native 層的 Looper 之間使用 epoll 進行通訊。當呼叫 Looper 的 loop()
方法的時候會啟動一個迴圈來對訊息進行處理。Java 層的 MQ 中沒有訊息的時候,Native 層的 Looper 會使其進入睡眠狀態,當有訊息到來的時候再將其喚醒起來處理訊息,以節省 CPU.
在 Looper 的 loop()
中開啟無限迴圈為什麼不會導致主執行緒 ANR 呢?這是因為 Android 系統本身就是基於訊息機制的,所謂的訊息就是指傳送到主執行緒當中的訊息。之所以產生 ANR 並不是因為主執行緒當中的任務無限迴圈,而是因為無限迴圈導致其他的事件得不到處理。
《Android 訊息機制:Handler、MessageQueue 和 Looper》
handler記憶體洩漏及解決辦法:如果 Handler 不是靜態內部類,Handler 會持有 Activity 的匿名引用。當 Activity 要被回收時,因為 Handler 在做耗時操作沒有被釋放,Handler Activity 的引用不能被釋放導致 Activity 沒有被回收停留在記憶體中造成記憶體洩露。
解決方法是:1). 將 Handler 設為靜態內部類;2). 使 Handler 持有 Activity 的弱引用;3). 在 Activity 生命週期 onDestroy()
中呼叫 Handler.removeCallback()
方法。
- 為什麼不能在子執行緒中訪問 UI?
Android 中的控制元件不是執行緒安全的,之所以這樣設計是為了:1).設計成同步的可以簡化使用的複雜度;2).可以提升控制元件的效能(非同步加鎖在非多執行緒環境是額外的開銷)。
- Handler.post() 的邏輯在哪個執行緒執行的,是由 Looper 所線上程還是 Handler 所線上程決定的?(這裡的 Handler 所在的執行緒指的是呼叫 Handler 的
post()
方法時 Handler 所在的執行緒) - Handler 的 post()/send() 的原理?
- Handler 的 post() 和 postDelayed() 方法的異同?
post()
方法所在的執行緒由 Looper 所線上程決定的;最終邏輯是在 Looper.loop()
方法中,從 MQ 中拿出 Message,並且執行其邏輯。這是在 Looper 中執行的。因此由 Looper 所線上程決定。
不論你呼叫 send()
型別的方法還是 post()
型別的方法,最終都會呼叫到 sendMessageAtTime()
方法。post()
和 postDelay()
的區別在於,前者使用當前時間,後者使用當前時間+delay 的時間來決定訊息觸發的時間。最終方法的引數都將被包裝成一個 Message 物件加入到 Handler 對應的 Looper 的 MQ 中被執行。
- Looper 和 Handler 一定要處於一個執行緒嗎?子執行緒中可以用 MainLooper 去建立 Handler嗎?
Looper 和 Handler 不需要再一個執行緒中,預設的情況下會從 TL 中取當前執行緒對應的 Looper,但我們可以通過顯式地指定一個 Looper 的方式來建立 Handler. 比如,當我們想要在子執行緒中傳送訊息到主執行緒中,那麼我們可以
Handler handler = new Handler(Looper.getMainLooper());
複製程式碼
- Handler.post() 方法傳送的是同步訊息嗎?可以傳送非同步訊息嗎?
使用者層面傳送的都是同步訊息,不能傳送非同步訊息;非同步訊息只能由系統傳送。
- MessageQueue.next() 會因為發現了延遲訊息,而進行阻塞。那麼為什麼後面加入的非延遲訊息沒有被阻塞呢?
- MessageQueue.enqueueMessage() 方法的原理,如何進行執行緒同步的?
- MessageQueue.next() 方法內部的原理?
- next() 是如何處理一般訊息的?
- next() 是如何處理同步屏障的?
- next() 是如何處理延遲訊息的?
呼叫 MessageQueue.next()
方法的時候會呼叫 Native 層的 nativePollOnce()
方法進行精準時間的阻塞。在 Native 層,將進入 pullInner()
方法,使用 epoll_wait
阻塞等待以讀取管道的通知。如果沒有從 Native 層得到訊息,那麼這個方法就不會返回。此時主執行緒會釋放 CPU 資源進入休眠狀態。
當我們加入訊息的時候,會呼叫 MessageQueue.enqueueMessage()
方法,新增完 Message 後,如果訊息佇列被阻塞,則會呼叫 Native 層的 nativeWake()
方法去喚醒。它通過向管道中寫入一個訊息,結束上述阻塞,觸發上面提到的 nativePollOnce()
方法返回,好讓加入的 Message 得到分發處理。
MessageQueue.enqueueMessage()
使用 synchronized 程式碼塊去進行同步。
資料:Android 中的 Handler 的 Native 層研究
- Handler 的
dispatchMessage()
分發訊息的處理流程?
使用 Handler 的時候我們會覆寫 Handler 的 handleMessage()
方法。當我們呼叫該 Handler 的 send()
或者 post()
傳送一個訊息的時候,傳送的資訊會被包裝成 Message,並且將該 Message 的 target 指向當前 Handler,這個訊息會被放進 Looper 的 MQ 中。然後在 Looper 的迴圈中,取出這個 Message,並呼叫它的 target Handler,也就是我們定義的 Handler 的 dispatchMessage()
方法處理訊息,此時會呼叫到 Handler 的 handleMessage()
方法處理訊息,並回撥 Callback.
- Handler 為什麼要有 Callback 的構造方法?
當 Handler 在訊息佇列中被執行的時候會直接呼叫 Handler 的 dispatchMessage()
方法回撥 Callback.
- Handler構造方法中通過
Looper.myLooper()
是如何獲取到當前執行緒的 Looper 的?
從 TL 中獲取
- MessageQueue 中底層是採用的佇列?
是單連結串列,不是佇列
- Looper 的兩個退出方法?
- quit() 和 quitSafely() 有什麼區別
- 子執行緒中建立了 Looper,在使用完畢後,終止訊息迴圈的方法?
- quit() 和 quitSafely() 的本質是什麼?
quit()
和 quitSafely()
的本質就是讓訊息佇列的 next()
返回 null,以此來退出Looper.loop()
。
quit()
呼叫後直接終止 Looper,不在處理任何 Message,所有嘗試把 Message 放進訊息佇列的操作都會失敗,比如 Handler.sendMessage()
會返回 false,但是存在不安全性,因為有可能有 Message 還在訊息佇列中沒來的及處理就終止 Looper 了。
quitSafely()
呼叫後會在所有訊息都處理後再終止 Looper,所有嘗試把 Message 放進訊息佇列的操作也都會失敗。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
void quit(boolean safe) {
if (!mQuitAllowed) throw new IllegalStateException("Main thread not allowed to quit.");
synchronized (this) {
if (mQuitting) return;
mQuitting = true;
if (safe) removeAllFutureMessagesLocked(); // 把所有延遲訊息清除
else removeAllMessagesLocked(); // 直接把訊息佇列裡面的訊息清空
nativeWake(mPtr);
}
}
複製程式碼
- Looper.loop() 在什麼情況下會退出?
1).next()
方法返回的 msg == null;2).執行緒意外終止。
- Looper.loop() 的原始碼流程?
- 獲取到 Looper 和訊息佇列;
- for 無限迴圈,阻塞於訊息佇列的
next()
方法; - 取出訊息後呼叫
msg.target.dispatchMessage(msg)
進行訊息分發。
Looper.loop()
方法執行時,如果內部的myLooper()
獲取不到Looper會出現什麼結果?
異常
- Android 如何保證一個執行緒最多隻能有一個 Looper?如何保證只有一個 MessageQueue
通過保證只有一個 Looper 來保證只有以一個 MQ. 在一個執行緒中使用 Handler 之前需要使用 Looper.prepare()
建立 Looper,它會從 TL 中獲取,如果發現 TL 中已經存在 Looper,就拋異常。
- Handler 訊息機制中,一個 Looper 是如何區分多個 Handler 的?
根據訊息的分發機制,Looper 不會區分 Handler,每個 Handler 會被新增到 Message 的 target 欄位上面,Looper 通過呼叫 Message.target.handleMessage()
來讓 Handler 處理訊息。
最後
關注作者,及時獲取更多高階面試題解 :)
Android 高階面試系列文章,關注作者及時獲取更多面試資料,
- Android 高階面試-1:Handler 相關
- Android 高階面試-2:IPC 相關
- Android 高階面試-3:語言相關
- Android 高階面試-4:虛擬機器相關
- Android 高階面試-5:四大元件、系統原始碼等
本系列以及其他系列的文章均維護在 Github 上面:Github / Android-notes,歡迎 Star & Fork. 如果你喜歡這篇文章,願意支援作者的工作,請為這篇文章點個贊?!