Android 高級面試-1:Handler 相關

要點

難點:html

  1. MQ 的 next() 方法,enqueueMessage() 方法,由於它們與 Native 層的 Looper 和 MQ 關聯。

重點:java

  1. 消息如何分發
  2. next() 方法
  3. 如何退出
  4. 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 的實現類來處理。)git

當咱們在某個線程當中調用 new Handler() 的時候會使用當前線程的 Looper 建立 Handler. 當前線程的 Looper 存在於線程局部變量 ThreadLocal 中。在使用 Handler 以前咱們須要先調用 Looper.prepare() 方法實例化當前線程的 Looper,並將其放置到當前線程的線程局部變量中(只放一次,之後會先從 TL 中獲取再使用,此時會調用 Looper 的構造方法,並在構造方法中初始化 MQ),而後調用 Looper.loop() 開啓消息循環。主線程也是同樣,只是主線程的 Looper 在 ActivityThread 的 main() 方法中被實例化。咱們可使用 Looper.getMainLooper() 方法來獲取主線程的 Looper,並使用它來建立 Handler,這樣咱們就能夠在任何線程中向主線程發送消息了。github

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).能夠提高控件的性能(異步加鎖在非多線程環境是額外的開銷)。oop

  • 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() 的源碼流程?
  1. 獲取到 Looper 和消息隊列;
  2. for 無限循環,阻塞於消息隊列的 next() 方法;
  3. 取出消息後調用 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 高級面試系列文章,關注做者及時獲取更多面試資料

本系列以及其餘系列的文章均維護在 Github 上面:Github / Android-notes,歡迎 Star & Fork. 若是你喜歡這篇文章,願意支持做者的工做,請爲這篇文章點個贊👍!

相關文章
相關標籤/搜索