Handler 都沒搞懂,拿什麼去跳槽啊?

0. 前言

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

能夠說只要有異步線程與主線程通訊的地方就必定會有 Handlerandroid

那麼,Handler 的通訊機制的背後的原理是什麼?面試

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

注意:本文所展現的系統源碼基於 Android-27 ,並有所刪減。性能優化

1. 重識 Handler

咱們可使用 Handler 發送並處理與一個線程關聯的 Message 和 Runnable 。(注意:Runnable 會被封裝進一個 Message,因此它本質上仍是一個 Message )架構

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

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 了,我就很少繞彎子了。async

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

  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面試題及答案!

羣裏免費分享給有須要的朋友,但願可以幫助一些在這個行業發展迷茫的,或者想系統深刻提高以及困於瓶頸的朋友,在網上博客論壇等地方少花些時間找資料,把有限的時間,真正花在學習上,因此我在這免費分享一些架構資料及給你們。但願在這些資料中都有你須要的內容。

相關文章
相關標籤/搜索