在Android應(yīng)用中,消息機制可謂是處于舉足輕重的地步,因為UI是Android的整個門面展示,而UI的展示是交由消息機制來處理。Android不允許在子線程中進行UI處理,因為這樣會引發(fā)多線程的安全問題,而解決這個問題則需要做加鎖等操作,這樣會導(dǎo)致效率低下,造成UI不流暢等問題,這是萬萬不可接受的。
說到Android消息機制的用途,你可能會想到子線程和主線程的通信、延遲發(fā)送一個消息或執(zhí)行一個Runnable等,但你有沒有想過,它是如何實現(xiàn)子線程和主線程的通信?子線程和子線程之間是否能通過消息機制來進行通信?延遲發(fā)送或執(zhí)行的內(nèi)部原理又是如何實現(xiàn)的?另外你可能聽過這樣的問題——主線程在Looper.loop()中開啟了一個死循環(huán),為什么不會造成ANR(Application Not Responding)?從MessageQueue中取出消息時可能會阻塞,為什么該阻塞也不會造成ANR?這些問題歸根結(jié)底就是原理問題,在看完本篇文章后都會茅塞頓開,so follow me!
Android消息機制的簡單圖解

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