全面剖析Android訊息機制原始碼
在Android應用中,訊息機制可謂是處於舉足輕重的地步,因為UI是Android的整個門面展示,而UI的展示是交由訊息機制來處理。Android不允許在子執行緒中進行UI處理,因為這樣會引發多執行緒的安全問題,而解決這個問題則需要做加鎖等操作,這樣會導致效率低下,造成UI不流暢等問題,這是萬萬不可接受的。
說到Android訊息機制的用途,你可能會想到子執行緒和主執行緒的通訊、延遲傳送一個訊息或執行一個 Runnable
等,但你有沒有想過,它是如何實現子執行緒和主執行緒的通訊?子執行緒和子執行緒之間是否能通過訊息機制來進行通訊?延遲傳送或執行的內部原理又是如何實現的?另外你可能聽過這樣的問題——主執行緒在 Looper.loop()
中開啟了一個死迴圈,為什麼不會造成 ANR(Application Not Responding)
?從 MessageQueue
中取出訊息時可能會阻塞,為什麼該阻塞也不會造成ANR?這些問題歸根結底就是原理問題,在看完本篇文章後都會茅塞頓開,so follow me!
Android訊息機制的簡單圖解

image
訊息的傳送到處理可以大致分為5個步驟,分別是 初始化準備工作 、 傳送訊息 、 訊息入隊 、 Looper
迴圈和訊息出隊 ,以及 訊息處理 ,我們一步一步來看。
1. 初始化準備工作
平時我們在使用 Handler
傳送訊息時,只需要建立一個 Handler
物件,然後呼叫相應的傳送方法即可,使用起來特別簡單。但其實在建立 Handler
物件之前,主執行緒已經做了一些準備工作,其中就有 MessageQueue
和 Looper
的建立初始化,並且將它們存放在主執行緒的私有記憶體中。接下來從原始碼中分析,首先來看 Handler
的構造方法:
1.1 Handler中的初始化工作
// 構造方法1 public Handler() { this(null, false); } // 構造方法2 public Handler(Callback callback) { this(callback, false); } // 構造方法3 public Handler(Callback callback, boolean async) { ...省略部分程式碼 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } // 構造方法4 public Handler(Looper looper) { this(looper, null, false); } // 構造方法5 public Handler(Looper looper, Callback callback) { this(looper, callback, false); } // 構造方法6 public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
有6個構造方法,我們先主要看 構造方法1
和 構造方法3
,其餘構造方法會在後面講解。其中, 構造方法1
呼叫了 構造方法3
,然後在 構造方法3
中,注意 mLooper = Looper.myLooper()
這行程式碼,獲取了一個 Looper
物件,然後接下來就對該 Looper
物件進行了 null
判斷,如果為 null
則丟擲 RunTime異常
:
Can't create handler inside thread xxx that has not called Looper.prepare()
因為沒有呼叫 Looper.preapre()
方法,所以在 xxx
這個執行緒中不能建立 Handler
物件
你會想哎這不對啊?我平時建立 Handler
時也沒遇到過啊。其實前面說過了,主執行緒早已幫我們做了這些初始化的準備工作了,具體的程式碼需要去 Looper
類裡看看。
1.2 Looper的初始化工作
首先看下 Looper
類的構造方法
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在 Looper
的構造方法裡,建立了一個 MessageQueue
物件,獲取了當前的 Thread
物件。但該構造方法是私有的,如何建立 Looper
物件呢?其實在上一小結中的 Runtime異常
中已經告訴了答案,即呼叫 Looper.prepare()
方法:
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"); } sThreadLocal.set(new Looper(quitAllowed)); }
其中 prepare()
方法呼叫了 prepare(boolean quitAllowed)
方法,而該方法裡也只有3行程式碼。首先判斷當前執行緒是否已經建立了 Looper
物件,如果是則拋異常:
Only one Looper may be created per thread
否則建立一個,並且將其存放到當前執行緒的私有記憶體中。如果你對 ThreadLocal
不太熟悉且想進一步瞭解的話,可以閱讀 Java之ThreadLocal詳解 這篇文章。
prepare()
方法的作用就是在當前執行緒中建立一個 Looper
物件,並且建立關聯一個 MessageQueue
物件,然後通過 ThreadLocal
將這個關聯了 MessageQueue
物件的 Looper
物件存放到當前執行緒的私有記憶體中,請記住,這是實現執行緒間通訊的根本。文章後面會將這塊同整個訊息機制串聯起來,屆時就會很清楚地理解了整個訊息機制邏輯。
另外,主執行緒的初始化 Looper
物件的方法如下,基本上和 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
類中的 main()
方法中呼叫,這是應用的入口方法,啟動時便會呼叫。所以說主執行緒的訊息傳送不需要手動呼叫 Looper.prepare()
方法,因為主執行緒早就做了這些準備工作。
// ActivityThread類,此方法為應用程式的入口方法 public static void main(String[] args) { ...省略部分程式碼 // 建立初始化Looper Looper.prepareMainLooper(); ...省略部分程式碼 if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } ...省略部分程式碼 // 開啟訊息迴圈 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
注意到該方法中倒數第二行呼叫了 Looper.loop()
方法,它是一個死迴圈,會一直呼叫訊息佇列 MessageQueue
的 next()
方法獲取 Message
,然後交由 Handler
處理。此處先知道其作用即可,後面第4章節會詳細介紹 Looper.loop()
方法。
1.3 訊息機制的初始化準備工作小結
現在我們來整理下,一個完整的訊息機制的初始化準備工作基本上有以下3個步驟:
- 呼叫
Looper.prepare()
方法,建立一個關聯了MessageQueue
的Looper
物件,並通過ThreadLocal
將其存放在當前執行緒的私有記憶體中,這是保證多執行緒間通訊的根本; - 建立一個
Handler
物件; - 呼叫
Looper.loop()
方法,開啟死迴圈從MessageQueue
中獲取訊息,該方法的呼叫時機也可以放在步驟2之前。
以上便是訊息機制的初始化準備工作,接下來便可以進行傳送訊息的操作了。
2. 傳送訊息
初始化準備過程已經完成了,接下來就可以傳送訊息。在傳送一條訊息時,我們可以呼叫 Handler
的以下 send
方法來實現:
2.1 傳送訊息原始碼分析
// 傳送方法1.傳送一條訊息 public final boolean sendMessage(Message msg){ return sendMessageDelayed(msg, 0); } // 傳送方法2.傳送一條延遲處理的訊息 public final boolean sendMessageDelayed(Message msg, long delayMillis){ if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } // 傳送方法3.傳送一條空訊息 public final boolean sendEmptyMessage(int what){ return sendEmptyMessageDelayed(what, 0); } // 傳送方法4.傳送一條延遲處理的空訊息 public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
也可以呼叫 post
方法來投遞一個 Runnable
,但其本質上也是傳送了一條訊息:
// 傳送方法5.投遞一個Runnable public final boolean post(Runnable r){ returnsendMessageDelayed(getPostMessage(r), 0); } // 傳送方法6.投遞一個延遲處理的Runnable public final boolean postDelayed(Runnable r, long delayMillis){ return sendMessageDelayed(getPostMessage(r), delayMillis); } // 將Runnable轉為Message private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
方法5
和 方法6
雖然是投遞一個 Runnable
,但實質上是通過 getPostMessage(Runnable r)
方法,將 Runnable
封裝到了 Message
的 callback
變數中,最終也是傳送了一個 Message
。
上面6種傳送訊息的方法,其中
方法1
內部呼叫了 方法2
;
方法3
呼叫了 方法4
,而 方法4
內部也呼叫了 方法2
;
方法5
和 方法6
內部也是呼叫了 方法2
;
可以看到send方法或post方法最終都指向了 方法2
,那麼接下來就分析 方法2——sendMessageDelayed(Message msg, long delayMillis)
:
public final boolean sendMessageDelayed(Message msg, long delayMillis){ if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } 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); } // 訊息入隊 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } // MessageQueue的訊息入隊 return queue.enqueueMessage(msg, uptimeMillis); }
可以看到, sendMessageDelayed(Message msg, long delayMillis)
方法內部呼叫了 sendMessageAtTime(Message msg, long uptimeMillis)
方法,其中引數 uptimeMillis
是一個時間參考,用來表示什麼時候該 Message
會被執行。
一條延遲處理的訊息,其對應的執行時間 uptimeMillis
等於開機執行時間 SystemClock.uptimeMillis()
加上延遲執行的時間 delayMillis
(非延遲訊息的 delayMillis
值為0),最終呼叫 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
方法。
接下來在 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
方法中,會將當前 Handler
物件封裝至 Message
的 target
變數,注意此處,後面在第五章節訊息處理時會再回顧這行程式碼。最後呼叫 MessageQueue
的 enqueueMessage(Message msg, long when)
方法中,進行訊息入隊操作。至此, Handler
中的訊息傳送過程已經完成了。
2.2 傳送訊息過程小結
傳送訊息的過程還是比較簡單的,簡單整理如下:
- 通過
post
系列方法或send
系列方法傳送一個訊息Message
; - 如果是延遲訊息,則該訊息的
執行時間=開機執行時間+延遲執行時間
,否則執行時間=開機執行時間
; - 最後將當前
Handler
物件封裝至Message
的target
中,再呼叫MessageQueue
的enqueueMessage(Message msg, long when)
方法進行入隊操作。
3. 訊息入隊
訊息傳送完畢,接下來就是訊息入隊操作,對應的程式碼是 MessageQueue
的 enqueueMessage()
方法:
3.1 訊息入隊原始碼分析
boolean enqueueMessage(Message msg, long when) { ...省略部分程式碼 synchronized (this) { ...省略部分程式碼 msg.markInUse(); msg.when = when; // 獲取Message佇列的頭部 // 注意:此佇列實質上是一個單向連結串列,目的是為了更方便地插入和移除訊息 Message p = mMessages; boolean needWake; // 滿足以下3個條件之一,便會將當前Message設為佇列頭: // 1.佇列頭為空,即該佇列為空; // 2.when為0,該值可以手動賦值,一般我們用不到; // 3.當前要入隊的訊息執行的時間早於佇列頭 if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. // 一個新的佇列頭,如果當前佇列阻塞則喚醒,mBocked為true表示佇列是阻塞狀態 msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 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. // 一般來說不需要喚醒佇列的阻塞狀態,除非佇列頭是一個同步屏障(barrier),且當前的Message是非同步的,則根據阻塞狀態決定是否需要喚醒佇列 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; // 該迴圈的目的是按照when從小到大的順序,找到Message的位置 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; } // We can assume mPtr != 0 because mQuitting is false. // mPtr是native層的MessageQueue的引用地址,是在MessageQueue的構造方法裡初始化的 // 這樣便可以將native層和java層的物件關聯起來 // 如果needWake=true,則通過nativeWake(mPtr)方法喚醒阻塞中的佇列,喚醒之後的操作,將在下節訊息出隊中講解 if (needWake) { nativeWake(mPtr); } } return true; }
訊息入隊的操作還是相對來說比較簡單的,即:
如果當前訊息佇列為空,或插入的 Message
執行時間 when
早於佇列頭的 Message
,則將其置為訊息佇列首部,並且將佇列從阻塞狀態中喚醒;
否則按照 Message
的執行時間排序,將該 Message
插入到佇列中。
注意到 needWake = mBlocked && p.target == null && msg.isAsynchronous()
這行程式碼,涉及到訊息機制的同步屏障,這裡簡單講解一下。
3.2 同步屏障(Sync Barrier)
在UI執行緒中,其主要目的就是保證及時有效地重新整理UI。假設現在需要重新整理UI,但主執行緒的訊息佇列中還存在其它的訊息,那麼就需要保證優先執行UI重新整理的訊息,遮蔽其它非UI相關的,同步屏障就起到了這樣的作用。
3.2.1 原始碼分析
一般來說我們傳送訊息時,最終會在 Handler
的 enqueueMessage()
方法中將當前Handler物件封裝至 Message
的 target
中,但同步屏障訊息是沒有 Handler
的,可以呼叫 MessageQueue
的 postSyncBarrier()
來發送一個訊息屏障:
public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
可以看到內部並沒有設定給 Message
設定 Handler
,而且依舊是按照訊息的執行時間 when
來排序插入到佇列中。移除同步屏障呼叫 MessageQueue
的 removeSyncBarrier(int token)
方法即可,其內部原始碼就不貼出來了,感興趣可自行檢視。
3.2.1 同步屏障和同步、非同步訊息
一般我們傳送的訊息是同步(synchronous)的,有兩種方式可以設定傳送非同步訊息:
- 一是通過
Handler
的構造方法3
、構造方法6
,將構造引數async
設為true
即可。通過這種方式,傳送的所有訊息都是非同步的。 - 另一種是呼叫
Message
的setAsynchronous(boolean async)
方法設定為true
。通過這種方式,當前傳送的訊息是非同步的。
同步屏障的作用就是遮蔽訊息佇列中該同步屏障之後的所有同步訊息,只處理非同步訊息,保證非同步訊息優先執行,其具體程式碼邏輯見 4.2 訊息出隊 。
3.2.3 同步屏障的應用
同步屏障用於UI繪製,在 ViewRootImpl
類的 scheduleTraversals()
方法中呼叫:
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; // UI繪製之前設定一個同步屏障 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 傳送繪製的訊息,保證優先執行mTraversalRunnable // 最終會將該Runnable物件封裝至Message中,並設定該Message為非同步訊息 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
當優先執行了 mTraversalRunnable
,呼叫其 run()
方法後, run()
方法內部會呼叫 doTraversal()
方法,該方法內移除了之前設定的同步屏障,然後執行UI繪製操作方法 performTraversals()
:
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // 移除之前設定的同步屏障 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } // 進行UI繪製 performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
4. Looper迴圈和訊息出隊
在1.3小節的訊息機制初始化準備小節中,我們提到了 Looper.loop()
呼叫,其作用是開啟一個訊息迴圈,然後從 MessageQueue
佇列中取出訊息交由 Handler
處理。把它放到現在來講是因為 loop()
方法和訊息出隊 next()
操作緊密相連,我們先看 loop()
方法內的實現:
4.1 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的next()方法會阻塞 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ...省略部分程式碼 try { // 進行訊息處理 // 此target便是Handler#enqueueMessage(MessageQueue, Message, long)方法中第一行程式碼 msg.target = this msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } ...省略部分程式碼 // Message回收 msg.recycleUnchecked(); } }
該方法內部實現還是比較簡單的:首先做了一些檢驗工作,然後開啟一個死迴圈。在死迴圈中呼叫 MessageQueue
的 next()
方法獲取訊息,如果有則交由其封裝的 Handler
處理(其處理邏輯見 5. 訊息處理 ),沒有則阻塞。具體的阻塞和訊息出隊,都在 MessageQueue
的 next()
方法實現,進去看一看吧。
4.2 訊息出隊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. // 在3.1 訊息入隊原始碼分析章節中,我們知道了mPtr是native層的MessageQueue的引用地址 // 通過這個引用地址,可以將native層和java層關聯起來 final long ptr = mPtr; if (ptr == 0) { return null; } // 用於統計當前閒置Handler數量 int pendingIdleHandlerCount = -1; // -1 only during first iteration // 阻塞的時長 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // 實現阻塞,阻塞時長為nextPollTimeoutMillis,Looper.loop()方法中的might block就是來自這裡 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; // msg.target == null表示該Message是一個屏障(barrier)。 // 如果是屏障,則跳過該屏障之後所有的同步訊息,只執行非同步訊息 if (msg != null && msg.target == null) { // Stalled by a barrier.Find the next asynchronous message in the queue. // 從佇列中找出下一個非同步Message do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready.Set a timeout to wake up when it is ready. // 該Message執行時間還未到,所以需要設定阻塞時長 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. // 取出需要執行的Message mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. // 如果當前佇列沒有訊息,則將nextPollTimeoutMillis設為-1 nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // 訊息佇列為空或Message未到執行時間時,則開始處理IdleHandler // 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)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // 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 { // 執行IdleHandler中的queueIdle()方法 keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // 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; } }
next()
方法程式碼內部也是一個死迴圈,程式碼比較長,我們分成兩部分邏輯來分析:一部分是前半段查詢獲取訊息的邏輯,另一部分是為後半段處理 IdleHandler
的邏輯。
4.2.1 查詢獲取訊息
在死迴圈內,首先判斷佇列頭是否為訊息屏障,是則找出下一個非同步的訊息,否則取佇列頭訊息。然後判斷取出的訊息執行時間 when
:
如果執行時間沒到,則設定阻塞時長,等下次迴圈時進行阻塞,否則取出該訊息並立刻返回。
阻塞的程式碼為 nativePollOnce(ptr, nextPollTimeoutMillis)
,這是一個 native
方法, nextPollTimeoutMillis
表示延遲時長:
- nextPollTimeoutMillis=0 :首次執行next()方法的死迴圈時,呼叫
nativePollOnce(ptr, nextPollTimeoutMillis)
方法,會立刻返回不會阻塞,然後繼續執行後面的程式碼; - nextPollTimeoutMillis=-1 :當佇列為空時,
nativePollOnce(ptr, nextPollTimeoutMillis)
會一直阻塞,除非有訊息入隊則觸發喚醒; - nextPollTimeoutMillis>0 :阻塞
nextPollTimeoutMillis
毫秒,在這期間如果有新的訊息入隊則可能觸發喚醒(新的訊息執行時間早於nextPollTimeoutMillis
則會喚醒)。
喚醒的操作由第3節訊息入隊的 nativeWake(mPtr)
方法實現。入隊喚醒和出隊阻塞的方法都是 native
方法,由 Linux
的 epoll
機制實現,感興趣可閱讀 《深入理解Android 卷III》第二章 深入理解Java Binder和MessageQueue 這篇文章中的2.3小節。
4.2.2 處理IdleHandler
當訊息佇列為空或 Message
未到執行時間時,則處理 IdleHandler
。 IdleHandler
可用於訊息佇列閒置時的處理,例如 ActivityThread
中的 GcIdler
,用於觸發主執行緒中的 GC
垃圾回收,當主執行緒沒有訊息處理時,就會有可能觸發 GC
。
// ActivityThread類中的GcIdler內部類 final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { doGcIfNeeded(); return false; } }
4.3 Looper迴圈和訊息出隊小結
在這一章節中, Looper.loop()
迴圈方法主要是呼叫 MessageQueue
的 next()
方法獲取 Message
,然後交由對應的 Hanlder
處理。
MessageQueue
佇列如果為空,則一直阻塞,等待下次訊息入隊喚醒佇列;不為空時,當訊息的執行時間未到,則進行 nextPollTimeoutMillis>0
時長的阻塞,直到阻塞時間結束,或有新的訊息入隊,且其執行時間早於當前阻塞的訊息執行時間,則喚醒佇列。
接下來則看最後一個步驟,關於訊息的處理邏輯。
5. 訊息處理
在 Looper.loop()
方法中,從 MessageQueue
中獲取到一條不為空的訊息時,呼叫了 msg.target.dispatchMessage(msg)
進行訊息分發處理,此時又回到了 Handler
中,看下 dispatchMessage(Message msg)
方法:
// Handler.java public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
首先會判斷 msg.callback
是否為 null
,這個 callback
就是封裝的 Runnable
物件,即 Hanlder.post
系列方法投遞的 Runnable
。如果不為空,則執行 Runnable
的 run()
方法。
否則,則判斷 mCallback
是否為 null
。這個 mCallback
是什麼東西呢?可以回顧下 Handler
的構造方法,其中 構造方法2、3、5、6
都有一個構造引數 Callback
,這個一個介面:
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); }
如果 Callback
介面的方法 handleMessage(Message msg)
返回為 true
,則不再繼續分發訊息,否則呼叫 Handler
的 handlerMessage(Message msg)
方法,這是一個空方法,一般選擇在 Handler
的子類實現:
public void handleMessage(Message msg) { }
一句話總結訊息處理的邏輯:
- 如果是
post
系列方法,則執行其Runnable
的run()
方法; - 否則判斷
Handler
構造方法裡傳入的Callback
是否返回為ture
:-
true
則訊息處理結束; -
false
則繼續分發給Handler
的handleMessage(Message msg)
,然後結束。
-
6. Handler移除訊息原始碼分析
當需要移除一個 Message
或 Runnable
時,呼叫 Handler
對應的 remove
方法即可,其內部呼叫的是 MessageQueue
對應的 remove
方法。我們選擇 Handler.removeMessages(int what)
這個方法來分析,其它移除邏輯基本一致。
// Handler.java // 移除訊息佇列中所有滿足Message.what=what的訊息 public final void removeMessages(int what) { mQueue.removeMessages(this, what, null); } // MessageQueue.java // 上面Handler的remove方法呼叫的是該方法 void removeMessages(Handler h, int what, Object object) { if (h == null) { return; } synchronized (this) { Message p = mMessages; // Remove all messages at front. // 此while迴圈移除回收了從佇列頭開始,連續滿足移除條件的訊息 while (p != null && p.target == h && p.what == what && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } // Remove all messages after front. // 否則在此while迴圈中移除回收之後的訊息 while (p != null) { Message n = p.next; if (n != null) { if (n.target == h && n.what == what && (object == null || n.obj == object)) { Message nn = n.next; n.recycleUnchecked(); p.next = nn; continue; } } p = n; } } }
主要是用了兩個 while
迴圈來移除訊息,第一個移除前面連續滿足移除條件的訊息,後面則依次判斷移除滿足條件的訊息。
注意這裡面有個暗坑,如果佇列裡有延遲執行的訊息,其中有通過 sendDelay
傳送的 what=0
的訊息,也有通過 postDelay
投遞的 Runnable
,如果呼叫 Handler.removeMessages(0)
方法來移除 what=0
的所有訊息,很不幸你會發現,佇列中的所有 Runnable
封裝的訊息也會被移除。原因是封裝 Runnable
的 Message
,其 what
預設為0,正好滿足移除 what=0
訊息的邏輯,所以定義 what
時需要注意,避免定義為0。
7. 訊息傳送到處理完整過程
一個完整的訊息機制從開始到結束的細節差不多就分析完了,現在我們將整個過程串起來,簡要回顧一番:
- 初始化準備 :手動呼叫
Looper.prepare()
方法初始化建立一個Looper
物件,其內部會同時建立了一個與之關聯的MessageQueue
物件,然後通過ThreadLocal
將該Looper
物件存放至當前執行緒的私有記憶體中。接著手動建立一個Handler
,用於傳送和處理訊息,可以通過構造方法傳入之前建立的Looper
;也可以不傳,則會使用當前執行緒私有記憶體中存放的Looper
物件。接著手動呼叫Looper.loop()
方法,開啟一個死迴圈,會一直呼叫MessageQueue
的next()
方法獲取訊息。 - 傳送訊息 :手動呼叫
send
系列方法或post
方法,最終會將訊息的延遲時間加上當前開機後的時長,作為該訊息的執行時間;進入sendMessageAtTime()
方法,將當前Handler
封裝至Message
中,然後呼叫MessageQueue
的enqueueMessage()
方法進行入隊操作。 - 訊息入隊 :按照上步中的執行時間排序,將訊息插入到
MessageQueue
佇列中,如果佇列為空,或者該訊息的執行時間早於佇列中的所有訊息執行時間,則喚醒佇列的阻塞狀態 - 訊息出隊 :由於初始化準備工作中已經開啟了
Looper
迴圈,所以當MessageQueue
中有訊息到了需要執行的時候,則會通過next()
方法返回一個Message
進行訊息分發。在next()
方法中,如果佇列為空或者佇列中的訊息執行時間都未到,則會導致死迴圈進入阻塞狀態。 - 訊息處理 :如果是
post
系列的方法,則呼叫其Runnable
物件的run()
方法,否則判斷Handler
構造方法傳入的Callback
介面實現方法handleMessage()
是否返回true
,是則結束訊息處理,否則再交由Handler
的dispatchMessage()
方法進行最後的處理。
8. Q & A
現在可以回答文章開頭的問題了:
-
Q: 主執行緒和子執行緒之間是如何實現通訊的?
A: 在主執行緒建立的
Handler
關聯了主執行緒私有的Looper
和MessageQueue
,然後Handler
在子執行緒傳送的Message
進入到了主執行緒的MessageQueue
,最終在主執行緒裡通過Looper.loop()
方法從MessageQueue
中獲取Message
,交由Handler
處理。 -
Q: 子執行緒和子執行緒之間能否通過訊息機制來通訊?
A: 能。需要在接收訊息的子執行緒裡,建立
Handler
之前需要手動呼叫Looper.prepare()
,之後呼叫Looper.loop()
方法,這樣便可以在另一個子執行緒中傳送訊息到該子執行緒了。 -
Q: 延遲傳送或執行的內部原理又是如何實現的?
A: 延遲的訊息會將開機執行時間加上延遲時間所得到的時間作為訊息的執行時間,進入訊息佇列後按照執行時間來排序插入佇列中,出隊時會通過
nativePollOnce()
方法在底層實現阻塞狀態,阻塞時長為訊息執行時間減去當前開機時長的差值,待阻塞狀態結束後便會讓該訊息出隊,並且交由Handler
來分發處理。 -
Q: 主執行緒在
Looper.loop()
中開啟了一個死迴圈,為什麼不會造成ANR
?從MessageQueue
中取出訊息時可能會阻塞,為什麼該阻塞也不會造成ANR
?A: 首先,
ANR
是因為輸入事件得不到及時處理,此外還有Serveice
、Broadcast
等,我們統一稱之為訊息事件。當訊息事件傳送了卻在規定的時間內無法得到處理,就會產生ANR
現象。主執行緒呼叫Looper.loop()
方法開啟一個死迴圈,其目的就是用於分發處理這些訊息事件,所以自然不會造成ANR
,除非有其它訊息事件做了耗時操作,才會有可能導致ANR
發生。從
MessageQueue
中取出訊息時可能會阻塞,什麼情況下會阻塞呢?佇列為空或沒有需要及時處理的訊息時,才會發生阻塞,這是為了節約CUP
資源不讓它空轉。如果你此時輸入一個訊息時間,阻塞狀態就會被喚醒,該事件會進行入隊出隊分發處理操作,也就談不上不及時處理,自然不會導致ANR
發生。
9. 結束語
Android訊息機制原始碼分析基本上已經結束了,由於技術水平有限及時間倉促,難免會有錯誤之處,還懇請指點出來,共同學習進步!