16c0641a5f63bed0.pngjava
Android 的消息機制主要指的是 Handler 的運行機制,從開發者的角度來講 Handler 是 Android 消息機制的上層接口,而底層的邏輯則是由 MessageQueue、 Looper 來完成的。android
Handler 的設計目的是爲了解決不能在 Android 主線程中作耗時操做而又只有主線程才能訪問 UI 的矛盾。經過 Handler 消息機制可讓開發者在子線程中完成耗時操做的同時在主線程中更新UI。算法
Handler 機制是 Android 用於 UI 刷新的一套消息機制。開發者可使用這套機制達到線程間通訊、線程切換目的。shell
這裏要思考一個問題:爲何 Android 非要規定只有主線程才能更新 UI 呢?安全
由於 Android 的全部 View 控件都不是線程安全的,若是在多線程中併發訪問極可能形成意想不到的結果。對於加鎖這種方案也不可取,首先加鎖以後會讓 UI 訪問邏輯變的很複雜,開發者須要時刻考慮多線程併發將會帶來的問題,其次鎖機制過重了它會嚴重影響 UI 訪問效率。介於這兩個缺點,最簡單且高效的方法就是採用單線程的方式訪問 UI。Handler 機制應運而生。數據結構
那麼 Handler 內部是如何完成線程切換的呢?答案就是神奇的 :ThreadLocal多線程
ThreadLocal 並非 Thread ,他的特色頗有意思: 每個線程存儲的值是相互隔離的併發
public class TreadLocalDemo { // 就算設置爲 static 結果也是同樣的 ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>(); public void runDemo() { mThreadLocal.set(true); System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get()); new Thread("Thread#1") { @Override public void run() { super.run(); mThreadLocal.set(false); System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get()); } }.start(); new Thread("Thread#2") { @Override public void run() { super.run(); System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get()); } }.start(); System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get()); } }
運行的結果很清晰的展現他的特色,雖然在主線程和線程1中都作了賦值操做,但並不能改變原來線程的賦值狀況。app
image-20190905231656636.pngless
對於 ThreadLocal 的原理簡單來講:每一線程都有一個專門用於保存 ThreadLocal 的成員變量 localValues
。 儘管在不一樣線程中訪問同一個 ThreadLocal 的 set
和 get
方法,但所作的操做都僅限制於各自線程的內部。這就是 ThreadLocal 能夠在多個線程中互不干擾的存儲和讀取數據的緣由。正是這種特性讓 Handler 作到了線程的切換。
Looper 正是藉助 ThreadLocal 的特色在不一樣的線程建立不一樣的實例。至此 Handler 與 Looper 、線程達到了一一對應的綁定關係。因此不管此 Handler 的實例在什麼線程調用,最終的回調都會分發到建立線程。
MessageQueue 主要有兩個操做:插入和讀取。讀取操做也會伴隨着刪除。插入和讀取的方法分別對應的是:enquequeMessage
和 next
,MessageQueue 並非像名字同樣使用隊列做爲數據結構,而是使用單鏈表來維護消息。單鏈表在插入和刪除上比較有優點。
首先來講說 next
方法。
Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { // 可見只有在調用 quit() 方法以後纔會返回空 return null; } ...... // 一個死循環 ! for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // 一個 native 方法,此方法在沒有消息或者消息沒有到執行時間的時候會讓線程進入等待狀態。 // 有點相似於 Object.wait 可是 nativePollOnce 能夠自定等待時間 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { ...... if (!keep) { synchronized (this) { // 獲取消息後從列表中移除 mIdleHandlers.remove(idler); } } } }
最關鍵的是三點內容
nativePollOnce
是一個 native 方法,若是單列表中沒有消息或者等待的時間沒有到,那麼當前線程將會被設置爲 wait 等待狀態 ,直到能夠獲取到下一個 Message
。更詳細的內容能夠參見 StackOverflow 上關於 nativePollOnce的回答而這個死循環的目的就是不讓 next
方法退出,等待 nativePollOnce 的響應。等到獲取到消息以後再將這個消息從消息列表中移除。
enqueueMessage 方法的主要工做就是向單鏈表中插入數據,當線程處於等待狀態則調用 nativeWake
喚醒線程,讓 next 方法處理消息。
boolean enqueueMessage(Message msg, long when) { ...... // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }
詳情請參見Handler是怎麼作到消息延時發送的 下面再抄一部分結論:
在 next 方法中若是頭部的這個 Message 是有延遲並且延遲時間沒到的(now < msg.when
),會計算一下時間(保存爲變量 nextPollTimeoutMillis
), 而後在循環開始的時候判斷若是這個 Message 有延遲,就調用nativePollOnce (ptr, nextPollTimeoutMillis)
進行阻塞。nativePollOnce()
的做用相似與 Object.wait()
, 只不過是使用了 Native 的方法對這個線程精確時間的喚醒。
postDelay()
一個10秒鐘的 Runnable A、消息進隊,MessageQueue 調用 nativePollOnce()
阻塞,Looper 阻塞;post()
一個 Runnable B、消息進隊,判斷如今A時間還沒到、正在阻塞,把B插入消息隊列的頭部(A的前面),而後調用 nativeWake()
方法喚醒線程;MessageQueue.next()
方法被喚醒後,從新開始讀取消息鏈表,第一個消息B無延時,直接返回給 Loopernext()
方法,MessageQueue 繼續讀取消息鏈表,第二個消息A還沒到時間,計算一下剩餘時間(假如還剩 9秒)繼續調用 nativePollOnce()
阻塞;直到阻塞時間到或者下一次有Message 進隊;image-20190908155849230.png
Looper 在 Android 消息機制中扮演着消息循環的角色。具體來講他的任務就是不停的從 MessageQueue 中獲取消息,若是有新消息就當即處理,沒有消息的時候,與 Looper 綁定的線程就會被 MessageQueue 的 next 的 nativePollOne 方法置於等待狀態。
/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // 建立 Looper 實例,將實例保存在 sThreadLocal 中與當前線程綁定。 sThreadLocal.set(new Looper(quitAllowed)); }
在構造方法裏面他會建立一個 MessageQueque,並保存當前線程。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
getMainLooper 能夠在任何地方獲取到主線程的 Looper,那麼主線程是如何建立 Looper 的呢?
咱們的目光來到了 AndroidThread 類, 在 AndroidThread 中咱們看到了熟悉的方法 :main(String[] args
。千萬不要被 AndroidThread 的名字所迷惑,AndroidThread 並非一個線程,它只是一個開啓主線程的類。
public static void main(String[] args) { .... // 建立 Looper 和 MessageQueue 對象,用於處理主線程的消息 Looper.prepareMainLooper(); // 建立 ActivityThread 對象 ActivityThread thread = new ActivityThread(); // 創建 Binder 通道 (建立新線程) thread.attach(false); // 消息循環運行 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
注意調用的是 prepare(false)
不容許退出,這是爲何呢?
public static void prepareMainLooper() { // 不容許退出 prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
這是由於主線程的 Looper 伴隨着一個 App 的整個生命週期,全部的 UI訪問、View 刷新都是在 Looper 裏面完成的,若是容許開發者手動退出,那麼整個 App 都會變得不可控。
更多細節能夠參見下面的一節「 Looper中的死循環爲何沒有卡死線程」
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper(); ...... final MessageQueue queue = me.mQueue; ...... // 死循環 for (;;) { // 可能會被阻塞 Message msg = queue.next(); if (msg == null) { // msg 爲 null 會當即退出循環,這也是退出循環的惟一方法。 return; } ...... try { // 開始分發消息 msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } ...... } }
loop 方法是一個死循環,他的工做就是不斷的檢查 MessageQueue 是否有能夠處理的消息,若是有這將消息分發給 Handler 處理。既然是死循環那麼爲何沒有卡死線程呢?更多細節能夠參見下面的一節「 Looper中的死循環爲何沒有卡死線程」
Looper 內部提供了兩種退出的方法,分別是 quit、quitSafely。從本質上來說 quit 調用後會當即退出 Looper,而 quitSafely 只是設定一個退出標記,等待消息隊列中的已有消息處理完畢後,再退出。
Looper 退出後,經過 Handler 發送的消息會失敗,這個時候 Handler send 方法會返回 false。在子線程中,若是手動爲其建立了 Looper,那麼在全部的邏輯完成後理應手動調用 quit 方法終止 Looper 內部的循環,不然這個子線程會一直處於等待狀態,而退出 Looper 以後,子線程也就會隨之終止,所以在子線程中使用 Looper,==必須在恰當的時機終止它==。
/** * Quits the looper. */ public void quit() { mQueue.quit(false); } /** * Quits the looper safely. */ public void quitSafely() { mQueue.quit(true); }
若是是主線程開發者就退出不了,要是退出了,就麻煩大了。
public static void prepareMainLooper() { // fasle 不容許退出 prepare(false); .... }
在 Looper.quit
的源碼中能夠清晰看到,本質上調用的是 MessageQueue 的 quite 方法。而在調用 MessageQueue.quite
以後 再次調用 MessageQueue.next()
會返回 null
Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { // 可見只有在調用 quit() 方法以後纔會返回空 return null; } ......
Looper.loop()
在調用 queue.next()
得的結果爲 null 的時候會當即跳出死循環, 這也是退出死循環的惟一方式。
public static void loop() { …… for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ……
參考知乎:Android中爲何主線程不會由於Looper.loop()裏的死循環卡死?
咱們都知道:一個簡單的死循環會消耗掉大量資源致使線程被卡死。可是 Looper.loop() 方法開啓就是一個死循環,它爲何沒有卡死線程呢?總結一下主要有3個疑惑:
首先要說說的是爲何在 Looper 中使用死循環。在 CPU 看來操做系統線程(這裏的定義能夠參見《Java基礎》多線程和線程同步 —— 進程與線程一節 ) 只不過是一段能夠執行的代碼,CPU 會使用 CFS 調度算法,保證每個 task 都儘量公平的享用 CPU 時間片。既然操做系統線程是一段能夠執行的代碼,當可執行的代碼結束以後,線程生命週期也就終止,線程將會退出。可是對於 Android 這類的 GUI 程序,咱們絕對不但願代碼執行一段時間以後主線程就本身中止掉,那麼如何保證線程一直執行下去呢?簡單的作法就是在線程中執行死循環,讓線程一直工做下去不會中止退出。
總的來講,在線程中使用死循環想要解決的問題就是防止線程本身退出。因此對於 Looper 而言,他的死循環就是但願不斷的從 MessageQueue 中獲取消息,而不但願線程線性執行以後就退出。
首先 Android 全部的 UI 刷新和生命週期的回調都是由 Handler消息機制完成的,就是說 UI 刷新和生命週期的回調都是依賴 Looper 裏面的死循環完成的,這樣設計的目的上文已經闡述清楚。這篇文章裏面貼了 AndroidTread 對於 Handler 的實現類 H 的源碼(進入文章後搜索:內部類H的部分源碼) 源碼太長,我就不貼了。
其次Looper 不是一直拼命幹活的傻小子,而是一個有活就乾沒活睡覺的老司機,因此主線程的死循環並非一直佔據着 CPU 的資源不釋放,不會形成過分消耗資源的問題。這裏涉及到了Linux pipe/epoll機制,簡單說就是在主線程的 MessageQueue 沒有消息時,便在 loop 的 queue.next() 中的 nativePollOnce() 方法裏讓線程進入休眠狀態,此時主線程會釋放CPU資源,直到下個消息到達或者有事務發生纔會再次被喚醒。因此 Looper 裏的死循環,沒有一直空輪詢着瞎忙,也不是進入阻塞狀態佔據着 CPU 不釋放,而是進入了會釋放資源的等待狀態,等待着被喚醒
通過上面的討論能夠得知:
那麼喚醒 Looper 的消息是從哪裏來的呢?
目光回到 AndroidThread 類中的這幾行代碼
public static void main(String[] args) { .... // 建立ActivityThread對象 ActivityThread thread = new ActivityThread(); //創建Binder通道 (建立新線程) thread.attach(false); Looper.loop(); //消息循環運行 }
在建立 ActivityThread 後會經過thread.attach(false)
方法在 ActivityThread 中建立 Binder 的服務端用於接收系統服務AMS發送來的事件,而後經過 ActivityThread 的內部類 ApplicationThread 中 sendMessage 方法
...... public final void scheduleStopActivity(IBinder token, boolean showWindow, int configChanges) { sendMessage( showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE, token, 0, configChanges); } public final void scheduleWindowVisibility(IBinder token, boolean showWindow) { sendMessage( showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW, token); } public final void scheduleSleeping(IBinder token, boolean sleeping) { sendMessage(H.SLEEPING, token, sleeping ? 1 : 0); } public final void scheduleResumeActivity(IBinder token, int processState, boolean isForward, Bundle resumeArgs) { updateProcessState(processState, false); sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0); ......
將消息發送給 AndroidThread 的 Handler 實現內部類 H。從而完成了 Binder Thread 到 UI 線程即主線程的切換,喚醒 Looper 進行 dispatchMessage 的動做。
喚醒的具體操做參見上文「MessageQueue -> enqueueMessage -> nativeWake」
經過 ActivityThread 的源碼能夠清楚看到
public static void main(String[] args) { .... //建立Looper和MessageQueue對象,用於處理主線程的消息 Looper.prepareMainLooper(); .... Looper.loop(); //消息循環運行 .... }
Android 在啓動一個 App 的時候都會建立一個 Looper,而用戶啓動子線程的時候是沒有這個操做的,因此須要開發者本身建立並調用 Looper.loop() 讓 Looper 運行起來。
new Thread("Thread#1") { @Override public void run() { // 手動生成爲當前線程生成 Looper Looper.prepare(); Handler handler = new Handler(); Looper.loop(); } }.start();
此處咱們作個實驗,既然 Looper 是個死循環那麼在 loop() 以後的代碼是否是永遠沒有機會執行呢?
/** * Android 消息機制 —— Handler * <p> * Created by im_dsd on 2019-09-07 */ public class HandlerDemo { public static final String TAG = "HandlerDemo"; private Handler mHandler; private Looper mLooper; /** * 如何在子線程中開啓 Handler */ public void startThreadHandler() { new Thread("Thread#1") { @Override public void run() { // 手動生成爲當前線程生成 Looper Looper.prepare(); mLooper = Looper.myLooper(); mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.d(TAG,Thread.currentThread().getName() + " " + msg.what); } }; Log.d(TAG,Thread.currentThread().getName() + "loop 開始 會執行嗎? "); // 手動開啓循環 Looper.loop(); Log.d(TAG,Thread.currentThread().getName() + "loop 結束 會執行嗎? "); } }.start(); // 等待線程啓動 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(TAG,"start send message"); mHandler.sendEmptyMessage(1); mHandler.post(() -> Log.d(TAG,Thread.currentThread().getName())); } }
自啓動到將 App 完全殺死,輸出結果也是如此:loop 後面的代碼沒有執行!
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 開始 會執行嗎? 2964-2964/com.example.dsd.demo D/HandlerDemo: start send message 2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1 1 2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1
這意味兩個嚴重的問題:looper()
後面的代碼一直都不會執行並且線程 Thread#1 將會一直運行下去!在 JVM 規範裏面規定==處於運行中的線程會不被 GC==。在沒有消息的時候 Looper 會處於等待狀態。等待在 Thread 的生命週期裏仍然屬於運行狀態,它永遠不會被 GC。
因此不少網上不少文章裏都有一個致命的缺陷,根本就沒有說起到要在使用完畢後即便退出 Looper。緊接上文的代碼
// 嘗試 1秒 後中止 try { Thread.sleep(1000); mLooper.quit(); } catch (InterruptedException e) { e.printStackTrace(); }
此時的結果
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 開始 會執行嗎? 2964-2964/com.example.dsd.demo D/HandlerDemo: start send message 2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1 1 2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1 2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 結束 會執行嗎?
根據綜上所述,Handler 機制徹底能夠在 Android 中用做線程間的消息同步,這裏要強調一下,Handler 機制是 Android 獨有的,筆者在寫上面的 Demo 的時候居然傻傻的將 Handler 的啓動放在了 Java 中,直接拋出了 RuntimException Stub 的錯誤。
總結一下在子線程中使用 Handler 機制要注意兩點問題:
Looper.prepare();
手動生成爲當前線程生成 Looper,並調用Looper.looper()
啓動內部的死循環。Looper.myLooper().quit()
退出當前線程。Handler 的工做主要就是發送和接收消息。消息的發送能夠經過 post 的一系列方法和 send 的一系類方法。在建立 Handler 的時候他會默認使用當前線程的 Looper ,若是當前線程沒有建立過 Looper 會拋出以下異常。
image-20190908125220700.png
固然也能夠手動指定不一樣線程的 Looper。
Handler mHandler = new Handler(Looper.getMainLooper());
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageAtTime(msg, uptimeMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
通過一系列的跟蹤,最終的結果是調用了enqueueMessage(MessageQueue, Message, long)
方法,目的就是爲了向 MessageQueue 中插入一條消息。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
然後 nativeWake
將會喚醒等待的線程,MessageQueue#next
將會在Looper.loop()
中將這條消息返回,Looper.loop()
在收到這條消息以後最終會交由 Handler#dispatchMessage
處理
/** * Looper 的 loop 方法 */ public static void loop() { ...... // 死循環 for (;;) { ...... // 開始分發消息 msg.target 指的就是發送消息的 Handler msg.target.dispatchMessage(msg); ...... } }
/** * Handle 的 dispatchMessage 方法 */ public void dispatchMessage(Message msg) { // 首先檢查 msg 的 callback 是否爲 null if (msg.callback != null) { // 不爲 null 使用 msg 的 callback 處理消息 handleCallback(msg); } else { // mCallback 是否爲 null if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } // 都沒有指定則交由開發者重寫的 handleMessage 處理 handleMessage(msg); } }
從上面的邏輯咱們能夠看出 callback 的優先級:msg#callback > new Handler(Callback) 中 指定的 Callback> 重寫 Handler 的 callBack
mCallback
指的是一個接口 , 可使用 Handler handler = new Handler(Callback)
的方式指定回調,這種方式能夠由外部傳遞進來會回調方法,更加靈活。
* Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. */ public interface Callback { /** * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ public boolean handleMessage(Message msg); }
至此對於 Android 的消息機制已經講解完畢,你是否已經有了清晰的認識呢?對於開篇的問題:Handler 是如何完成線程切換的,你找到答案了嗎?
16c0641ad370a90a.jpeg
mThread 是主線程,這裏會檢查當前線程是不是主線程。
這個問題緣由出如今 Activity 的生命週期中 , 在 onCreate 方法中, UI 處於建立過程,對用戶來講界面還不可見,直到 onStart 方法後界面可見了,再到 onResume 方法後頁面能夠交互,從某種程度來說, 在 onCreate 方法中不能算是更新 UI,只能說是配置 UI,或者是設置 UI 屬性。 這個時候不會調用到 ViewRootImpl.checkThread () , 由於 ViewRootImpl 沒有建立。 而在 onResume 方法後, ViewRootImpl 才被建立。 這個時候去交戶界面纔算是更新 UI。
setContentView 知識創建了 View 樹,並無進行渲染工做 (其實真正的渲染工做實在 onResume 以後)。也正是創建了 View 樹,所以咱們能夠經過 findViewById() 來獲取到 View 對象,可是因爲並無進行渲染視圖的工做,也就是沒有執行 ViewRootImpl.performTransversal。一樣 View 中也不會執行 onMeasure (), 若是在 onResume() 方法裏直接獲取 View.getHeight() / View.getWidth () 獲得的結果老是 0 解決方案是在 UI 真正可見的方法 onWindowFocusChanged()
裏面獲取。
若是在 Handler 構造方法裏面直接 new Looper(), 多是沒法保證 Looper 惟一,只有用 Looper.prepare() 才能保證惟一性,具體能夠看 prepare 方法。
由於一個線程只綁定一個 Looper ,因此在 Looper 構造方法裏面初始化就能夠保證 mQueue 也是惟一的 Thread 對應一個 Looper 對應一個 mQueue。
這個方法裏面是一個死循環,可是裏面的方法 nativePollOnce 運用了 Linux 的 epoll 機制,在沒有消息的時候回會將線程掛起,注意此時的掛起至關於 Object.wait() : 它會釋放 CPU 資源,等待喚醒。有消息進入的時候回到用 MessageQueue#enqueueMessage() 加入數據,此時 MessageQueue#enqueueMessage() 內部的 nativeWeak 會從新喚醒線程,
能夠發現:
MessageQueue#next() 中會判斷消息的時間,若是尚未到消息執行的時間,會將消息定時掛起(咱們記錄它 爲 A。若是這個時候有新的消息到來(記錄爲 B), MessageQueue#enqueueMessage() 會按照消息的執行時間排序插入,而後喚醒 MessageQueue#next() 處理消息。若是 B 不是定時消息當即處理,若是是定時消息更新掛起時間繼續阻塞,等到阻塞時間到的時候就會當即喚醒 next 方法處理。
Loop#loop 裏面的死循環是爲了防止退出的,而 MessageQueue#next() 的死循環是爲了確認到底有沒有消息 參考:對於 MessageQueue 的解讀
Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } //爲-1時,說明是第一次循環,在當前消息隊列中沒有MSG的狀況下,須要處理註冊的Handler int pendingIdleHandlerCount = -1; // -1 only during first iteration // 超時時間。即等待xxx毫秒後,該函數返回。若是值爲0,則無須等待當即返回。若是爲-1,則進入無限等待,直到有事件發生爲止 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) {//??? Binder.flushPendingCommands(); } // 該函數提供阻塞操做。若是nextPollTimeoutMillis爲0,則該函數無須等待,當即返回。 //若是爲-1,則進入無限等待,直到有事件發生爲止。 //在第一次時,因爲nextPollTimeoutMillis被初始化爲0,因此該函數會當即返回 //從消息鏈的頭部獲取消息 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 && msg.target == null) {//message不爲空,但沒有執行者 // Stalled by a barrier. Find the next asynchronous message in the queue. do {//尋找Asynchronous的消息 prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //判斷頭節點所表明的message執行的時間是否小於當前時間 //若是小於,讓loop()函數執行message分發過程。不然,須要讓線程再次等待(when–now)毫秒 // 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;//將隊列設置爲非 blocked 狀態 if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); msg.markInUse(); //將消息設置爲 inuse return msg; } } else { //若是頭節點爲空,消息鏈中無消息,設置nextPollTimeoutMillis爲-1,讓線程阻塞住, //直到有消息投遞(調用enqueueMessage方法),並利用nativeWake方法解除阻塞 // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { //第一次進入,當前無消息,或還須要等待一段時間消息才能分發,得到idle handler的數量 pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { //若是沒有idle handler須要執行,阻塞線程進入下次循環 // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf("MessageQueue", "IdleHandler threw exception", t); } //若是keep=false,代表此idler只執行一次,把它從列表中刪除。若是返回true,則表示下次空閒時,會再次執行 if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } //pendingIdleHandlerCount=0,是爲了不第二次循環時,再一次通知listeners //若是想剩餘的listeners再次被調用,只有等到下一次調用next()函數了 // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // nextPollTimeoutMillis=0,是爲了不在循環執行idler.queueIdle()時,有消息投遞。 //因此nextPollTimeoutMillis=0後,第二次循環在執行nativePollOnce時,會當即返回 //若是消息鏈中仍是沒有消息,那麼將會在continue;處執行完第二次循環,進行第三次循環,而後進入無限等待狀態 // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
重點:
死循環最多執行三次:
enqueueMessage 有排序功能,按照時間入隊。
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) {//若是正在退出,就不能插入消息 IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); msg.recycle();//把這個消息放回到消息池中 //得到msg時,先去消息池中看看有沒有被回收的msg,若是有,就不用建立新的msg了 return false; } msg.markInUse(); msg.when = when;//從消息隊列中取出絕對時間戳 Message p = mMessages;//指向隊首 boolean needWake; //若是當前的消息鏈爲空,或者要插入的MSG爲QUIT消息,或者要插入的MSG時間小於消息鏈的第一個消息 //在隊首插入 if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { //不然,咱們須要遍歷該消息鏈,將該MSG插入到合適的位置 // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } //neekWake=mBlocked, 若是mBlocked爲ture,表面當前線程處於阻塞狀態,即nativePollOnce處於阻塞狀態 //當經過enqueueMessage插入消息後,就要把狀態改成非阻塞狀態,因此經過執行nativeWake方法,觸發nativePollOnce函數結束等待 // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }
本文由博客羣發一文多發等運營工具平臺 OpenWrite 發佈