Android 高階面試-1:Handler 相關
難點:
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 處理訊息。
最後
關注+點贊+加群:185873940 ,免費獲取更多高階面試題解

我這裡還有許多免費的關於高階安卓學習資料,包括高階UI、效能優化、架構師課程、 NDK、混合式開發:ReactNative+Weex等多個Android技術知識的架構視訊資料,還有職業生涯規劃及面試指導。