android handler封装_Handler都没搞懂,你拿什么去跳槽啊?!
0. 前言
做 Android 開發(fā)肯定離不開跟 Handler 打交道,它通常被我們用來做主線程與子線程之間的通信工具,而 Handler 作為 Android 中消息機(jī)制的重要一員也確實給我們的開發(fā)帶來了極大的便利。
Handler應(yīng)用之廣泛,可以說只要有異步線程與主線程通信的地方就一定會有 Handler。
所以搞懂 Handler 對理解Android非常有必要。
那么,Handler 的通信機(jī)制的背后的原理是什么?
本文帶你揭曉。
注意:本文所展示的系統(tǒng)源碼基于 Android-27 ,并有所刪減。
1. 重識 Handler
我們可以使用 Handler 發(fā)送并處理與一個線程關(guān)聯(lián)的 Message 和 Runnable 。(注意:Runnable 會被封裝進(jìn)一個 Message,所以它本質(zhì)上還是一個 Message )
每個 Handler 都會跟一個線程綁定,并與該線程的 MessageQueue 關(guān)聯(lián)在一起,從而實現(xiàn)消息的管理以及線程間通信。
1.1 Handler 的基本用法
1android.os.Handler handler = new Handler(){2 @Override3 public void handleMessage(final Message msg) {4 //這里接受并處理消息5 }6};7//發(fā)送消息8handler.sendMessage(message);9handler.post(runnable);實例化一個 Handler 重寫 handleMessage 方法 ,然后在需要的時候調(diào)用它的 send 以及 post 系列方法就可以了,非常簡單易用,并且支持延時消息。(更多方法可查詢 API 文檔)
但是奇怪,我們并沒有看到任何 MessageQueue 的身影,也沒看到它與線程綁定的邏輯,這是怎么回事?
2. Handler 原理解析
相信大家早就聽說過了 Looper 以及 MessageQueue 了,我就不多繞彎子了。
不過在開始分析原理之前,先明確我們的問題:
2.1 Handler 與 Looper 的關(guān)聯(lián)
實際上我們在實例化 Handler 的時候 Handler 會去檢查當(dāng)前線程的 Looper 是否存在,如果不存在則會報異常,也就是說在創(chuàng)建 Handler 之前一定需要先創(chuàng)建 Looper 。
代碼如下:
1public Handler(Callback callback, boolean async) { 2 //檢查當(dāng)前的線程是否有 Looper 3 mLooper = Looper.myLooper(); 4 if (mLooper == null) { 5 throw new RuntimeException( 6 "Can't create handler inside thread that has not called Looper.prepare()"); 7 } 8 //Looper 持有一個 MessageQueue 9 mQueue = mLooper.mQueue;10}這個異常相信很多同學(xué)遇到過,而我們平時直接使用感受不到這個異常是因為主線程已經(jīng)為我們創(chuàng)建好了 Looper,先記住,后面會講。(見【3.2】)
一個完整的 Handler 使用例子其實是這樣的:
1class LooperThread extends Thread { 2 public Handler mHandler; 3 public void run() { 4 Looper.prepare(); 5 mHandler = new Handler() { 6 public void handleMessage(Message msg) { 7 // process incoming messages here 8 } 9 };10 Looper.loop();11 }12}Looper.prepare() :
1//Looper2private static void prepare(boolean quitAllowed) {3 if (sThreadLocal.get() != null) {4 throw new RuntimeException("Only one Looper may be created per thread");5 }6 sThreadLocal.set(new Looper(quitAllowed));7}Looper 提供了 Looper.prepare() 方法來創(chuàng)建 Looper ,并且會借助 ThreadLocal 來實現(xiàn)與當(dāng)前線程的綁定功能。Looper.loop() 則會開始不斷嘗試從 MessageQueue 中獲取 Message , 并分發(fā)給對應(yīng)的 Handler(見【2.3】)。
也就是說 Handler 跟線程的關(guān)聯(lián)是靠 Looper 來實現(xiàn)的。
2.2 Message 的存儲與管理
Handler 提供了一些列的方法讓我們來發(fā)送消息,如 send()系列 post()系列 。
不過不管我們調(diào)用什么方法,最終都會走到 Message.enqueueMessage(Message,long) 方法。
以 sendEmptyMessage(int) 方法為例:
1//Handler2sendEmptyMessage(int)3 -> sendEmptyMessageDelayed(int,int)4 -> sendMessageAtTime(Message,long)5 -> enqueueMessage(MessageQueue,Message,long)6 -> queue.enqueueMessage(Message, long);到了這里,消息的管理者 MessageQueue 也就露出了水面。
MessageQueue 顧明思議,就是個隊列,負(fù)責(zé)消息的入隊出隊。
2.3 Message 的分發(fā)與處理
了解清楚 Message 的發(fā)送與存儲管理后,就該揭開分發(fā)與處理的面紗了。
前面說到了 Looper.loop() 負(fù)責(zé)對消息的分發(fā),本章節(jié)進(jìn)行分析。
先來看看所涉及到的方法:
1//Looper 2public static void loop() { 3 final Looper me = myLooper(); 4 if (me == null) { 5 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 6 } 7 final MessageQueue queue = me.mQueue; 8 //... 9 for (;;) {10 // 不斷從 MessageQueue 獲取 消息11 Message msg = queue.next(); // might block12 //退出 Looper 13 if (msg == null) {14 // No message indicates that the message queue is quitting.15 return;16 }17 //...18 try {19 msg.target.dispatchMessage(msg);20 end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();21 } finally {22 //...23 }24 //...25 //回收 message, 見【3.5】26 msg.recycleUnchecked();27 }28}loop() 里調(diào)用了 MessageQueue.next() :
1//MessageQueue 2Message next() { 3 //... 4 for (;;) { 5 //... 6 nativePollOnce(ptr, nextPollTimeoutMillis); 7 8 synchronized (this) { 9 // Try to retrieve the next message. Return if found.10 final long now = SystemClock.uptimeMillis();11 Message prevMsg = null;12 Message msg = mMessages;13 //...14 if (msg != null) {15 if (now < msg.when) {16 // Next message is not ready. Set a timeout to wake up when it is ready.17 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);18 } else {19 // Got a message.20 mBlocked = false;21 if (prevMsg != null) {22 prevMsg.next = msg.next;23 } else {24 mMessages = msg.next;25 }26 msg.next = null;27 return msg;28 }29 } else {30 // No more messages.31 nextPollTimeoutMillis = -1;32 }3334 // Process the quit message now that all pending messages have been handled.35 if (mQuitting) {36 dispose();37 return null;38 }39 }4041 // Run the idle handlers. 關(guān)于 IdleHandler 自行了解42 //...43 }44}還調(diào)用了 msg.target.dispatchMessage(msg) ,msg.target 就是發(fā)送該消息的 Handler,這樣就回調(diào)到了 Handler 那邊去了:
1//Handler 2public void dispatchMessage(Message msg) { 3 //msg.callback 是 Runnable ,如果是 post方法則會走這個 if 4 if (msg.callback != null) { 5 handleCallback(msg); 6 } else { 7 //callback 見【3.4】 8 if (mCallback != null) { 9 if (mCallback.handleMessage(msg)) {10 return;11 }12 }13 //回調(diào)到 Handler 的 handleMessage 方法14 handleMessage(msg);15 }16}注意:dispatchMessage() 方法針對 Runnable 的方法做了特殊處理,如果是 ,則會直接執(zhí)行 Runnable.run() 。
分析:Looper.loop() 是個死循環(huán),會不斷調(diào)用 MessageQueue.next() 獲取 Message ,并調(diào)用 msg.target.dispatchMessage(msg) 回到了 Handler 來分發(fā)消息,以此來完成消息的回調(diào)。
注意:loop()方法并不會卡死主線程,見【6】。
那么線程的切換又是怎么回事呢?
很多人搞不懂這個原理,但是其實非常簡單,我們將所涉及的方法調(diào)用棧畫出來,如下:
1Thread.foo(){2 Looper.loop()3 -> MessageQueue.next()4 -> Message.target.dispatchMessage()5 -> Handler.handleMessage()6}顯而易見,Handler.handleMessage() 所在的線程最終由調(diào)用 Looper.loop() 的線程所決定。
平時我們用的時候從異步線程發(fā)送消息到 Handler,這個 Handler 的 handleMessage() 方法是在主線程調(diào)用的,所以消息就從異步線程切換到了主線程。
2.3 圖解原理
文字版的原理解析到這里就結(jié)束了,如果你看到這里還是沒有懂,沒關(guān)系,我特意給你們準(zhǔn)備了些圖,配合著前面幾個章節(jié),再多看幾遍,一定可以吃透。
handler-looper-mq.jpg
handler_java.jpg
圖片來源見【6】
2.4 小結(jié)
Handler 的背后有著 Looper 以及 MessageQueue 的協(xié)助,三者通力合作,分工明確。
嘗試小結(jié)一下它們的職責(zé),如下:
- Looper :負(fù)責(zé)關(guān)聯(lián)線程以及消息的分發(fā),會與創(chuàng)建它的線程綁定,并負(fù)責(zé)在該線程下從 MessageQueue 獲取 Message,分發(fā)給 Handler ;
- MessageQueue :是個隊列,負(fù)責(zé)消息的存儲與管理,負(fù)責(zé)管理由 Handler 發(fā)送過來的 Message ;
- Handler : 負(fù)責(zé)發(fā)送并處理消息,面向開發(fā)者,提供 API,并隱藏背后實現(xiàn)的細(xì)節(jié)。
對【2】章節(jié)提出的問題用一句話總結(jié):
Handler 發(fā)送的消息由 MessageQueue 存儲管理,并由 Loopler 負(fù)責(zé)回調(diào)消息到 handleMessage()。
線程的轉(zhuǎn)換由 Looper 完成,handleMessage() 所在線程由 Looper.loop() 調(diào)用者所在線程決定。
3. Handler 的延伸
Handler 雖然簡單易用,但是要用好它還是需要注意一點,另外 Handler相關(guān) 還有些鮮為人知的知識技巧,比如 IdleHandler。
由于 Handler 的特性,它在 Android 里的應(yīng)用非常廣泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。
這些我會講解一些,我沒講到的可以自行搜索相關(guān)內(nèi)容進(jìn)行了解。
3.1 Handler 引起的內(nèi)存泄露原因以及最佳解決方案
Handler 允許我們發(fā)送延時消息,如果在延時期間用戶關(guān)閉了 Activity,那么該 Activity 會泄露。
這個泄露是因為 Message 會持有 Handler,而又因為 Java 的特性,內(nèi)部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導(dǎo)致 Activity 泄露。
解決該問題的最有效的方法是:將 Handler 定義成靜態(tài)的內(nèi)部類,在內(nèi)部持有 Activity 的弱引用,并及時移除所有消息。
示例代碼如下:
1private static class SafeHandler extends Handler { 2 3 private WeakReference ref; 4 5 public SafeHandler(HandlerActivity activity) { 6 this.ref = new WeakReference(activity); 7 } 8 9 @Override10 public void handleMessage(final Message msg) {11 HandlerActivity activity = ref.get();12 if (activity != null) {13 activity.handleMessage(msg);14 }15 }16}并且再在 Activity.onDestroy() 前移除消息,加一層保障:
1@Override2protected void onDestroy() {3 safeHandler.removeCallbacksAndMessages(null);4 super.onDestroy();5}這樣雙重保障,就能完全避免內(nèi)存泄露了。
注意:單純的在 onDestroy 移除消息并不保險,因為 onDestroy 并不一定執(zhí)行。
3.2 為什么我們能在主線程直接使用 Handler,而不需要創(chuàng)建 Looper ?
前面我們提到了每個Handler 的線程都有一個 Looper ,主線程當(dāng)然也不例外,但是我們不曾準(zhǔn)備過主線程的 Looper 而可以直接使用,這是為何?
注意:通常我們認(rèn)為 ActivityThread 就是主線程。事實上它并不是一個線程,而是主線程操作的管理者,所以吧,我覺得把 ActivityThread 認(rèn)為就是主線程無可厚非,另外主線程也可以說成 UI 線程。
在 ActivityThread.main() 方法中有如下代碼:
1//android.app.ActivityThread 2public static void main(String[] args) { 3 //... 4 Looper.prepareMainLooper(); 5 6 ActivityThread thread = new ActivityThread(); 7 thread.attach(false); 8 9 if (sMainThreadHandler == null) {10 sMainThreadHandler = thread.getHandler();11 }12 //...13 Looper.loop();1415 throw new RuntimeException("Main thread loop unexpectedly exited");16}Looper.prepareMainLooper(); 代碼如下:
1/** 2 * Initialize the current thread as a looper, marking it as an 3 * application's main looper. The main looper for your application 4 * is created by the Android environment, so you should never need 5 * to call this function yourself. See also: {@link #prepare()} 6 */ 7public static void prepareMainLooper() { 8 prepare(false); 9 synchronized (Looper.class) {10 if (sMainLooper != null) {11 throw new IllegalStateException("The main Looper has already been prepared.");12 }13 sMainLooper = myLooper();14 }15}可以看到在 ActivityThread 里 調(diào)用了 Looper.prepareMainLooper() 方法創(chuàng)建了 主線程的 Looper ,并且調(diào)用了 loop() 方法,所以我們就可以直接使用 Handler 了。
注意:Looper.loop() 是個死循環(huán),后面的代碼正常情況不會執(zhí)行。
3.3 主線程的 Looper 不允許退出
如果你嘗試退出 Looper ,你會得到以下錯誤信息:
1Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.2 at android.os.MessageQueue.quit(MessageQueue.java:415)3 at android.os.Looper.quit(Looper.java:240)why? 其實原因很簡單,主線程不允許退出,退出就意味 APP 要掛。
3.4 Handler 里藏著的 Callback 能干什么?
在 Handler 的構(gòu)造方法中有幾個 要求傳入 Callback ,那它是什么,又能做什么呢?
來看看 Handler.dispatchMessage(msg) 方法:
1public void dispatchMessage(Message msg) { 2 //這里的 callback 是 Runnable 3 if (msg.callback != null) { 4 handleCallback(msg); 5 } else { 6 //如果 callback 處理了該 msg 并且返回 true, 就不會再回調(diào) handleMessage 7 if (mCallback != null) { 8 if (mCallback.handleMessage(msg)) { 9 return;10 }11 }12 handleMessage(msg);13 }14}可以看到 Handler.Callback 有優(yōu)先處理消息的權(quán)利 ,當(dāng)一條消息被 Callback 處理并攔截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不會被調(diào)用了;如果 Callback 處理了消息,但是并沒有攔截,那么就意味著一個消息可以同時被 Callback 以及 Handler 處理。
這個就很有意思了,這有什么作用呢?
我們可以利用 Callback 這個攔截機(jī)制來攔截 Handler 的消息!
場景:Hook ActivityThread.mH , 在 ActivityThread 中有個成員變量 mH ,它是個 Handler,又是個極其重要的類,幾乎所有的插件化框架都使用了這個方法。
3.5 創(chuàng)建 Message 實例的最佳方式
由于 Handler 極為常用,所以為了節(jié)省開銷,Android 給 Message 設(shè)計了回收機(jī)制,所以我們在使用的時候盡量復(fù)用 Message ,減少內(nèi)存消耗。
方法有二:
3.6 子線程里彈 Toast 的正確姿勢
當(dāng)我們嘗試在子線程里直接去彈 Toast 的時候,會 crash :
1java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 2本質(zhì)上是因為 Toast 的實現(xiàn)依賴于 Handler,按子線程使用 Handler 的要求修改即可(見【2.1】),同理的還有 Dialog。
正確示例代碼如下:
1new Thread(new Runnable() {2 @Override3 public void run() {4 Looper.prepare();5 Toast.makeText(HandlerActivity.this, "不會崩潰啦!總結(jié)
以上是生活随笔為你收集整理的android handler封装_Handler都没搞懂,你拿什么去跳槽啊?!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: css面试题
- 下一篇: 阿里巴巴技术大牛赏鉴