作 Android 開發確定離不開跟 Handler 打交道,它一般被咱們用來作主線程與子線程之間的通訊工具,而 Handler 做爲 Android 中消息機制的重要一員也確實給咱們的開發帶來了極大的便利。java
能夠說只要有異步線程與主線程通訊的地方就必定會有 Handler。android
那麼,Handler 的通訊機制的背後的原理是什麼?git
本文帶你揭曉。面試
注意:本文所展現的系統源碼基於 Android-27 ,並有所刪減。app
咱們可使用 Handler 發送並處理與一個線程關聯的 Message 和 Runnable 。(注意:Runnable 會被封裝進一個 Message,因此它本質上仍是一個 Message )框架
每一個 Handler 都會跟一個線程綁定,並與該線程的 MessageQueue 關聯在一塊兒,從而實現消息的管理以及線程間通訊。異步
android.os.Handler handler = new Handler(){
@Override
public void handleMessage(final Message msg) {
//這裏接受並處理消息
}
};
//發送消息
handler.sendMessage(message);
handler.post(runnable);
複製代碼
實例化一個 Handler 重寫 handleMessage
方法 ,而後在須要的時候調用它的 send
以及 post
系列方法就能夠了,很是簡單易用,而且支持延時消息。(更多方法可查詢 API 文檔)async
可是奇怪,咱們並無看到任何 MessageQueue 的身影,也沒看到它與線程綁定的邏輯,這是怎麼回事?ide
相信你們早就據說過了 Looper 以及 MessageQueue 了,我就很少繞彎子了。工具
不過在開始分析原理以前,先明確咱們的問題:
實際上咱們在實例化 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 來實現的。
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 顧明思議,就是個隊列,負責消息的入隊出隊。
瞭解清楚 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()
方法是在主線程調用的,因此消息就從異步線程切換到了主線程。
文字版的原理解析到這裏就結束了,若是你看到這裏仍是沒有懂,不要緊,我特地給大家準備了些圖,配合着前面幾個章節,再多看幾遍,必定能夠吃透。
Handler 的背後有着 Looper 以及 MessageQueue 的協助,三者通力合做,分工明確。
嘗試小結一下它們的職責,以下:
對【2】章節提出的問題用一句話總結:
Handler 發送的消息由 MessageQueue 存儲管理,並由 Loopler 負責回調消息到 handleMessage()。
線程的轉換由 Looper 完成,handleMessage() 所在線程由 Looper.loop() 調用者所在線程決定。
Handler 雖然簡單易用,可是要用好它仍是須要注意一點,另外 Handler相關 還有些不爲人知的知識技巧,好比 IdleHandler。
因爲 Handler 的特性,它在 Android 裏的應用很是普遍,好比: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。
這些我會講解一些,我沒講到的能夠自行搜索相關內容進行了解。
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
並不必定執行。
前面咱們提到了每一個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()
是個死循環,後面的代碼正常狀況不會執行。
若是你嘗試退出 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 要掛。
在 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,又是個極其重要的類,幾乎全部的插件化框架都使用了這個方法。
因爲 Handler 極爲經常使用,因此爲了節省開銷,Android 給 Message 設計了回收機制,因此咱們在使用的時候儘可能複用 Message ,減小內存消耗。
方法有二:
Message.obtain();
獲取;handler.obtainMessage();
。當咱們嘗試在子線程裏直接去彈 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();
複製代碼
咱們能夠利用 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();
}
}
複製代碼
可以省去很多樣板代碼。
由前文可得出一些知識點,彙總一下,方便記憶。
Looper.myLooper().quit();
退出;Handler.handleMessage()
所在的線程是 Looper.loop() 方法被調用的線程,也能夠說成 Looper 所在的線程,並非建立 Handler 的線程;Handler 簡單易用的背後藏着工程師大量的智慧,要努力向他們學習。
看完並理解本文能夠說你對 Handler 有了一個很是深刻且全面的瞭解,應對面試確定是綽綽有餘了。
Handler
what-is-the-relationship-between-looper-handler-and-messagequeue-in-android
Android消息機制1-Handler(Java 層)
Android中爲何主線程不會卡死