0. 前言

做 Android 開發肯定離不開跟 Handler 打交道,它通常被我們用來做主線程與子線程之間的通信工具,而 Handler 作為 Android 中消息機制的重要一員也確實給我們的開發帶來了極大的便利。

可以說只要有異步線程與主線程通信的地方就一定會有 Handler

那麼,Handler 的通信機制的背後的原理是什麼?

本文帶你揭曉。 文末有免費福利哦

注意:本文所展示的系統源碼基於 Android-27 ,並有所刪減。

1. 重識 Handler

我們可以使用 Handler 發送並處理與一個線程關聯的 Message 和 Runnable 。(注意:Runnable 會被封裝進一個 Message,所以它本質上還是一個 Message

每個 Handler 都會跟一個線程綁定,並與該線程的 MessageQueue 關聯在一起,從而實現消息的管理以及線程間通信。

1.1 Handler 的基本用法

android.os.Handler handler = new Handler(){
  @Override
  public void handleMessage(final Message msg) {
    //這里接受並處理消息
  }
};
//發送消息
handler.sendMessage(message);
handler.post(runnable);

實例化一個 Handler 重寫 handleMessage 方法 ,然後在需要的時候調用它的 send 以及 post 系列方法就可以了,非常簡單易用,並且支持延時消息。(更多方法可查詢 API 文檔)

但是奇怪,我們並沒有看到任何 MessageQueue 的身影,也沒看到它與線程綁定的邏輯,這是怎麼回事

2. Handler 原理解析

相信大家早就聽說過了 Looper 以及 MessageQueue 了,我就不多繞彎子了。

不過在開始分析原理之前,先明確我們的問題

  1. Handler 是如何與線程關聯的?
  2. Handler 發出去的消息是誰管理的?
  3. 消息又是怎麼回到 handleMessage() 方法的?
  4. 線程的切換是怎麼回事?

2.1 Handler 與 Looper 的關聯

實際上我們在實例化 Handler 的時候 Handler 會去檢查當前線程的 Looper 是否存在,如果不存在則會報異常,也就是說在創建 Handler 之前一定需要先創建 Looper

代碼如下:

public Handler(Callback callback, boolean async) {
        //檢查當前的線程是否有 Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //Looper 持有一個 MessageQueue
        mQueue = mLooper.mQueue;
}

這個異常相信很多同學遇到過,而我們平時直接使用感受不到這個異常是因為主線程已經為我們創建好了 Looper,先記住,後面會講。(見【3.2】)

一個完整的 Handler 使用例子其實是這樣的:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}

Looper.prepare() :

//Looper
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));
}
復制代碼

Looper 提供了 Looper.prepare() 方法來創建 Looper ,並且會藉助 ThreadLocal 來實現與當前線程的綁定功能。Looper.loop() 則會開始不斷嘗試從 MessageQueue 中獲取 Message , 並分發給對應的 Handler(見【2.3】)

也就是說 Handler 跟線程的關聯是靠 Looper 來實現的。

2.2 Message 的存儲與管理

Handler 提供了一些列的方法讓我們來發送消息,如 send()系列 post()系列 。文末有免費福利哦

不過不管我們調用什麼方法,最終都會走到 MessageQueue.enqueueMessage(Message,long) 方法。

sendEmptyMessage(int) 方法為例:

//Handler
sendEmptyMessage(int)
  -> sendEmptyMessageDelayed(int,int)
    -> sendMessageAtTime(Message,long)
      -> enqueueMessage(MessageQueue,Message,long)
            -> queue.enqueueMessage(Message, long);

到了這里,消息的管理者 MessageQueue 也就露出了水面
MessageQueue 顧明思議,就是個隊列,負責消息的入隊出隊。

2.3 Message 的分發與處理

了解清楚 Message 的發送與存儲管理後,就該揭開分發與處理的面紗了。

前面說到了 Looper.loop() 負責對消息的分發,本章節進行分析。文末有免費福利哦

先來看看所涉及到的方法:

//Looper
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    //...
    for (;;) {
       // 不斷從 MessageQueue 獲取 消息
        Message msg = queue.next(); // might block
        //退出 Looper 
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //...
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            //...
        }
        //...
                //回收 message, 見【3.5】
        msg.recycleUnchecked();
    }
}

loop() 里調用了 MessageQueue.next() :

//MessageQueue
Message next() {
    //...
    for (;;) {
        //...
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //...
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }
        }

        // Run the idle handlers. 關於 IdleHandler 自行了解
        //...
    }
}

還調用了 msg.target.dispatchMessage(msg) ,msg.target 就是發送該消息的 Handler,這樣就回調到了 Handler 那邊去了:

//Handler
public void dispatchMessage(Message msg) {
  //msg.callback 是 Runnable ,如果是 post方法則會走這個 if
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    //callback 見【3.4】
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    //回調到 Handler 的 handleMessage 方法
    handleMessage(msg);
  }
}

image.gif

注意:dispatchMessage() 方法針對 Runnable 的方法做了特殊處理,如果是 ,則會直接執行 Runnable.run()

分析:Looper.loop() 是個死循環,會不斷調用 MessageQueue.next() 獲取 Message ,並調用 msg.target.dispatchMessage(msg) 回到了 Handler 來分發消息,以此來完成消息的回調

注意:loop()方法並不會卡死主線程,見【6】。

那麼線程的切換又是怎麼回事呢?
很多人搞不懂這個原理,但是其實非常簡單,我們將所涉及的方法調用棧畫出來,如下:

Thread.foo(){
    Looper.loop()
     -> MessageQueue.next()
      -> Message.target.dispatchMessage()
       -> Handler.handleMessage()
}

顯而易見,Handler.handleMessage() 所在的線程最終由調用 Looper.loop() 的線程所決定。

平時我們用的時候從異步線程發送消息到 Handler,這個 Handler 的 handleMessage() 方法是在主線程調用的,所以消息就從異步線程切換到了主線程。

2.3 圖解原理

文字版的原理解析到這里就結束了,如果你看到這里還是沒有懂,沒關系,我特意給你們準備了些圖,配合著前面幾個章節,再多看幾遍,一定可以吃透。

圖片來源見【6】

2.4 小結

Handler 的背後有著 Looper 以及 MessageQueue 的協助,三者通力合作,分工明確。

嘗試小結一下它們的職責,如下:

  • Looper :負責關聯線程以及消息的分發在該線程下**從 MessageQueue 獲取 Message,分發給 Handler ;
  • MessageQueue :是個隊列,負責消息的存儲與管理,負責管理由 Handler 發送過來的 Message ;
  • Handler : 負責發送並處理消息,面向開發者,提供 API,並隱藏背後實現的細節。

對【2】章節提出的問題用一句話總結:

Handler 發送的消息由 MessageQueue 存儲管理,並由 Loopler 負責回調消息到 handleMessage()。

線程的轉換由 Looper 完成,handleMessage() 所線上程由 Looper.loop() 調用者所線上程決定。

3. Handler 的延伸

Handler 雖然簡單易用,但是要用好它還是需要注意一點,另外 Handler相關 還有些鮮為人知的知識技巧,比如 IdleHandler。

由於 Handler 的特性,它在 Android 里的應用非常廣泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。

這些我會講解一些,我沒講到的可以自行搜索相關內容進行了解。

3.1 Handler 引起的內存泄露原因以及最佳解決方案

Handler 允許我們發送延時消息,如果在延時期間用戶關閉了 Activity,那麼該 Activity 會泄露。

這個泄露是因為 Message 會持有 Handler,而又因為 Java 的特性,內部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導致 Activity 泄露。

解決該問題的最有效的方法是:將 Handler 定義成靜態的內部類,在內部持有 Activity 的弱引用,並及時移除所有消息

示例代碼如下:

private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}

並且再在 Activity.onDestroy() 前移除消息,加一層保障:

@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}

這樣雙重保障,就能完全避免內存泄露了。

注意:單純的在 onDestroy 移除消息並不保險,因為 onDestroy 並不一定執行。

3.2 為什麼我們能在主線程直接使用 Handler,而不需要創建 Looper ?

前面我們提到了每個Handler 的線程都有一個 Looper ,主線程當然也不例外,但是我們不曾準備過主線程的 Looper 而可以直接使用,這是為何?

注意:通常我們認為 ActivityThread 就是主線程。事實上它並不是一個線程,而是主線程操作的管理者,所以吧,我覺得把 ActivityThread 認為就是主線程無可厚非,另外主線程也可以說成 UI 線程。

在 ActivityThread.main() 方法中有如下代碼:

//android.app.ActivityThread
public static void main(String[] args) {
  //...
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  //...
  Looper.loop();

  throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper(); 代碼如下:

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

可以看到在 ActivityThread 里 調用了 Looper.prepareMainLooper() 方法創建了 主線程的 Looper ,並且調用了 loop() 方法,所以我們就可以直接使用 Handler 了。

注意:Looper.loop() 是個死循環,後面的代碼正常情況不會執行。

3.3 主線程的 Looper 不允許退出

如果你嘗試退出 Looper ,你會得到以下錯誤資訊:

Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
  at android.os.MessageQueue.quit(MessageQueue.java:415)
  at android.os.Looper.quit(Looper.java:240)

why? 其實原因很簡單,主線程不允許退出,退出就意味 APP 要掛。

3.4 Handler 里藏著的 Callback 能幹什麼?

在 Handler 的構造方法中有幾個 要求傳入 Callback ,那它是什麼,又能做什麼呢?

來看看 Handler.dispatchMessage(msg) 方法:

public void dispatchMessage(Message msg) {
  //這里的 callback 是 Runnable
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    //如果 callback 處理了該 msg 並且返回 true, 就不會再回調 handleMessage
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

可以看到 Handler.Callback 有優先處理消息的權利 ,當一條消息被 Callback 處理並攔截(返回 true),那麼 Handler 的 handleMessage(msg) 方法就不會被調用了;如果 Callback 處理了消息,但是並沒有攔截,那麼就意味著一個消息可以同時被 Callback 以及 Handler 處理

這個就很有意思了,這有什麼作用呢?

我們可以利用 Callback 這個攔截機制來攔截 Handler 的消息!

場景:Hook ActivityThread.mH , 在 ActivityThread 中有個成員變量 mH ,它是個 Handler,又是個極其重要的類,幾乎所有的插件化框架都使用了這個方法。

3.5 創建 Message 實例的最佳方式

由於 Handler 極為常用,所以為了節省開銷,Android 給 Message 設計了回收機制,所以我們在使用的時候盡量復用 Message ,減少內存消耗。

方法有二:

  1. 通過 Message 的靜態方法 Message.obtain(); 獲取;
  2. 通過 Handler 的公有方法 handler.obtainMessage();

3.6 子線程里彈 Toast 的正確姿勢

當我們嘗試在子線程里直接去彈 Toast 的時候,會 crash :

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

本質上是因為 Toast 的實現依賴於 Handler,按子線程使用 Handler 的要求修改即可(見【2.1】),同理的還有 Dialog。

正確示例代碼如下:

new Thread(new Runnable() {
  @Override
  public void run() {
    Looper.prepare();
    Toast.makeText(HandlerActivity.this, "不會崩潰啦!", Toast.LENGTH_SHORT).show();
    Looper.loop();
  }
}).start();

3.7 妙用 Looper 機制

我們可以利用 Looper 的機制來幫助我們做一些事情:

  1. 將 Runnable post 到主線程執行;
  2. 利用 Looper 判斷當前線程是否是主線程。

完整示例代碼如下:

public final class MainThread {

    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

}

能夠省去不少樣板代碼。

4. 知識點匯總

由前文可得出一些知識點,匯總一下,方便記憶。

  1. Handler 的背後有 Looper、MessageQueue 支撐,Looper 負責消息分發,MessageQueue 負責消息管理;
  2. 在創建 Handler 之前一定需要先創建 Looper;
  3. Looper 有退出的功能,但是主線程的 Looper 不允許退出;
  4. 異步線程的 Looper 需要自己調用 Looper.myLooper().quit(); 退出;
  5. Runnable 被封裝進了 Message,可以說是一個特殊的 Message;
  6. Handler.handleMessage() 所在的線程是 Looper.loop() 方法被調用的線程,也可以說成 Looper 所在的線程,並不是創建 Handler 的線程;
  7. 使用內部類的方式使用 Handler 可能會導致內存泄露,即便在 Activity.onDestroy 里移除延時消息,必須要寫成靜態內部類;

5. 總結

Handler 簡單易用的背後藏著工程師大量的智慧,要努力向他們學習。

看完並理解本文可以說你對 Handler 有了一個非常深入且全面的了解,應對面試肯定是綽綽有餘了。

最後給大家分享一份非常系統和全面的Android進階技術大綱及進階資料,及面試題集

想學習更多Android知識,請加入Android技術開發企鵝交流 7520 16839

進群與大牛們一起討論,還可獲取Android高級架構資料、源碼、筆記、視訊

包括 高級UI、Gradle、RxJava、小程序、Hybrid、移動架構、React Native、性能優化等全面的Android高級實踐技術講解性能優化架構思維導圖,和BATJ面試題及答案!

群里免費分享給有需要的朋友,希望能夠幫助一些在這個行業發展迷茫的,或者想系統深入提升以及困於瓶頸的朋友,在網上部落格論壇等地方少花些時間找資料,把有限的時間,真正花在學習上,所以我在這免費分享一些架構資料及給大家。希望在這些資料中都有你需要的內容。