1. 程式人生 > 其它 >Android高階進階之路【四】一文讀懂 Handler 機制

Android高階進階之路【四】一文讀懂 Handler 機制

前言


做 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.1Handler 與 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,先記住,後面會講。

一個完整的 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);
  }
}

注意: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 圖解原理

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

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 裡移除延時訊息,必須要寫成靜態內部類;

文章轉自 https://juejin.cn/post/6844903783139393550,如有侵權,請聯絡刪除。

相關視訊:

Android金九銀十面試重點問題解析——Handler原始碼詳細解析_嗶哩嗶哩_bilibili
【 Android進階教程】——Framework面試必問的Handler原始碼解析_嗶哩嗶哩_bilibili
【 Android進階教程】——熱修復原理解析_嗶哩嗶哩_bilibili
【 Android進階教程】——如何解決OOM問題與LeakCanary原理解析_嗶哩嗶哩_bilibili
【 Android進階教程】——OkHttp原理_嗶哩嗶哩_bilibili
【 Android進階教程】——終於有人可以將retrofit設計原理講清楚了_嗶哩嗶哩_bilibili
【 Android進階教程】——面試核心之WorkManager原理_嗶哩嗶哩_bilibili