在Android應用中,消息機制可謂是處於舉足輕重的地步,由於UI是Android的整個門面展現,而UI的展現是交由消息機制來處理。Android不容許在子線程中進行UI處理,由於這樣會引起多線程的安全問題,而解決這個問題則須要作加鎖等操做,這樣會致使效率低下,形成UI不流暢等問題,這是萬萬不可接受的。java
說到Android消息機制的用途,你可能會想到子線程和主線程的通訊、延遲發送一個消息或執行一個Runnable
等,但你有沒有想過,它是如何實現子線程和主線程的通訊?子線程和子線程之間是否能經過消息機制來進行通訊?延遲發送或執行的內部原理又是如何實現的?另外你可能聽過這樣的問題——主線程在Looper.loop()
中開啓了一個死循環,爲何不會形成ANR(Application Not Responding)
?從MessageQueue
中取出消息時可能會阻塞,爲何該阻塞也不會形成ANR?這些問題歸根結底就是原理問題,在看完本篇文章後都會茅塞頓開,so follow me!android
消息的發送處處理能夠大體分爲5個步驟,分別是初始化準備工做、發送消息、消息入隊、Looper
循環和消息出隊,以及消息處理,咱們一步一步來看。安全
平時咱們在使用Handler
發送消息時,只須要建立一個Handler
對象,而後調用相應的發送方法便可,使用起來特別簡單。但其實在建立Handler
對象以前,主線程已經作了一些準備工做,其中就有MessageQueue
和Looper
的建立初始化,而且將它們存放在主線程的私有內存中。接下來從源碼中分析,首先來看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異常
:app
Can't create handler inside thread xxx that has not called Looper.prepare()
由於沒有調用Looper.preapre()
方法,因此在xxx
這個線程中不能建立Handler
對象less
你會想哎這不對啊?我平時建立Handler
時也沒遇到過啊。其實前面說過了,主線程早已幫咱們作了這些初始化的準備工做了,具體的代碼須要去Looper
類裏看看。異步
首先看下Looper
類的構造方法async
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
在Looper
的構造方法裏,建立了一個MessageQueue
對象,獲取了當前的Thread
對象。但該構造方法是私有的,如何建立Looper
對象呢?其實在上一小結中的Runtime異常
中已經告訴了答案,即調用Looper.prepare()
方法:ide
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
對象,若是是則拋異常:oop
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()
方法。
如今咱們來整理下,一個完整的消息機制的初始化準備工做基本上有如下3個步驟:
- 調用
Looper.prepare()
方法,建立一個關聯了MessageQueue
的Looper
對象,並經過ThreadLocal
將其存放在當前線程的私有內存中,這是保證多線程間通訊的根本;- 建立一個
Handler
對象;- 調用
Looper.loop()
方法,開啓死循環從MessageQueue
中獲取消息,該方法的調用時機也能夠放在步驟2以前。
以上即是消息機制的初始化準備工做,接下來即可以進行發送消息的操做了。
初始化準備過程已經完成了,接下來就能夠發送消息。在發送一條消息時,咱們能夠調用Handler
的如下send
方法來實現:
// 發送方法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){
return sendMessageDelayed(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
中的消息發送過程已經完成了。
發送消息的過程仍是比較簡單的,簡單整理以下:
- 經過
post
系列方法或send
系列方法發送一個消息Message
;- 若是是延遲消息,則該消息的
執行時間=開機運行時間+延遲執行時間
,不然執行時間=開機運行時間
;- 最後將當前
Handler
對象封裝至Message
的target
中,再調用MessageQueue
的enqueueMessage(Message msg, long when)
方法進行入隊操做。
消息發送完畢,接下來就是消息入隊操做,對應的代碼是MessageQueue
的enqueueMessage()
方法:
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()
這行代碼,涉及到消息機制的同步屏障,這裏簡單講解一下。
在UI線程中,其主要目的就是保證及時有效地刷新UI。假設如今須要刷新UI,但主線程的消息隊列中還存在其它的消息,那麼就須要保證優先執行UI刷新的消息,屏蔽其它非UI相關的,同步屏障就起到了這樣的做用。
通常來講咱們發送消息時,最終會在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)
方法便可,其內部源碼就不貼出來了,感興趣可自行查看。
通常咱們發送的消息是同步(synchronous)的,有兩種方式能夠設置發送異步消息:
Handler
的構造方法3
、構造方法6
,將構造參數async
設爲true
便可。經過這種方式,發送的全部消息都是異步的。Message
的setAsynchronous(boolean async)
方法設置爲true
。經過這種方式,當前發送的消息是異步的。同步屏障的做用就是屏蔽消息隊列中該同步屏障以後的全部同步消息,只處理異步消息,保證異步消息優先執行,其具體代碼邏輯見4.2 消息出隊。
同步屏障用於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;
}
}
}
複製代碼
在1.3小節的消息機制初始化準備小節中,咱們提到了Looper.loop()
調用,其做用是開啓一個消息循環,而後從MessageQueue
隊列中取出消息交由Handler
處理。把它放到如今來說是由於loop()
方法和消息出隊next()
操做緊密相連,咱們先看loop()
方法內的實現:
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()
方法實現,進去看一看吧。
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
的邏輯。
在死循環內,首先判斷隊列頭是否爲消息屏障,是則找出下一個異步的消息,不然取隊列頭消息。而後判斷取出的消息執行時間when
:
若是執行時間沒到,則設置阻塞時長,等下次循環時進行阻塞,不然取出該消息並馬上返回。
阻塞的代碼爲nativePollOnce(ptr, nextPollTimeoutMillis)
,這是一個native
方法,nextPollTimeoutMillis
表示延遲時長:
nativePollOnce(ptr, nextPollTimeoutMillis)
方法,會馬上返回不會阻塞,而後繼續執行後面的代碼;nativePollOnce(ptr, nextPollTimeoutMillis)
會一直阻塞,除非有消息入隊則觸發喚醒;nextPollTimeoutMillis
毫秒,在這期間若是有新的消息入隊則可能觸發喚醒(新的消息執行時間早於nextPollTimeoutMillis
則會喚醒)。喚醒的操做由第3節消息入隊的nativeWake(mPtr)
方法實現。入隊喚醒和出隊阻塞的方法都是native
方法,由Linux
的epoll
機制實現,感興趣可閱讀《深刻理解Android 卷III》第二章 深刻理解Java Binder和MessageQueue 這篇文章中的2.3小節。
當消息隊列爲空或Message
未到執行時間時,則處理IdleHandler
。IdleHandler
可用於消息隊列閒置時的處理,例如ActivityThread
中的GcIdler
,用於觸發主線程中的GC
垃圾回收,當主線程沒有消息處理時,就會有可能觸發GC
。
// ActivityThread類中的GcIdler內部類
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
複製代碼
在這一章節中,Looper.loop()
循環方法主要是調用MessageQueue
的next()
方法獲取Message
,而後交由對應的Hanlder
處理。
MessageQueue
隊列若是爲空,則一直阻塞,等待下次消息入隊喚醒隊列;不爲空時,當消息的執行時間未到,則進行nextPollTimeoutMillis>0
時長的阻塞,直到阻塞時間結束,或有新的消息入隊,且其執行時間早於當前阻塞的消息執行時間,則喚醒隊列。
接下來則看最後一個步驟,關於消息的處理邏輯。
在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
的構造方法,其中構造方法二、三、五、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)
,而後結束。當須要移除一個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。
一個完整的消息機制從開始到結束的細節差很少就分析完了,如今咱們將整個過程串起來,簡要回顧一番:
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()
方法進行最後的處理。如今能夠回答文章開頭的問題了:
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
發生。
Android消息機制源碼分析基本上已經結束了,因爲技術水平有限及時間倉促,不免會有錯誤之處,還懇請指點出來,共同窗習進步!