前言
該文主要是分析Handler消息機制的關(guān)鍵源碼,閱讀前需要對handler有一些基本的認(rèn)識。這里先簡要回顧一下:
基本組成
完整的消息處理機制包含四個要素:
- Message(消息):信息的載體
- MessageQueue(消息隊列):用來存儲消息的隊列
- Looper(消息循環(huán)):負(fù)責(zé)檢查消息隊列中是否有消息,并負(fù)責(zé)取出消息
- Handler(發(fā)送和處理消息):把消息加入消息隊列中,并負(fù)責(zé)分發(fā)和處理消息
基本使用方法
Handler的簡單用法如下:
Handler handler \= new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
Message message \= new Message();
handler.sendMessage(message);
注意在非主線程中的要調(diào)用Looper.prepare()和 Looper.loop()方法
工作流程
其工作流程如下圖所示:

從發(fā)送消息到接收消息的流程概括如下:
- 發(fā)送消息
- 消息進入消息隊列
- 從消息隊列里取出消息
- 消息的處理
下面就一折四個步驟分析一下相關(guān)源碼:
發(fā)送消息
handle有兩類發(fā)送消息的方法,它們在本質(zhì)上并沒有什么區(qū)別:
sendXxxx()
boolean sendMessage(Message msg)
boolean sendEmptyMessage(int what)
boolean sendEmptyMessageDelayed(int what, long delayMillis)
boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
boolean sendMessageDelayed(Message msg, long delayMillis)
boolean sendMessageAtTime(Message msg, long uptimeMillis)
boolean sendMessageAtFrontOfQueue(Message msg)
postXxxx()
boolean post(Runnable r)
boolean postAtFrontOfQueue(Runnable r)
boolean postAtTime(Runnable r, long uptimeMillis)
boolean postAtTime(Runnable r, Object token, long uptimeMillis)
boolean postDelayed(Runnable r, long delayMillis)
boolean postDelayed(Runnable r, Object token, long delayMillis)
這里不分析具體的方法特性,它們最終都是通過調(diào)用sendMessageAtTime()或者sendMessageAtFrontOfQueue實現(xiàn)消息入隊的操作,唯一的區(qū)別就是post系列方法在消息發(fā)送前調(diào)用了getPostMessage方法:
private static Message getPostMessage(Runnable r) {
Message m \= Message.obtain();
m.callback \= r;
return m;
}
需要注意的是:sendMessageAtTime()再被其他sendXxx調(diào)用時,典型用法為:
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
若調(diào)用者沒有指定延遲時間,則消息的執(zhí)行時間即為當(dāng)前時間,也就是立即執(zhí)行。Handler所暴露的方法都遵循這種操作,除非特別指定,msg消息執(zhí)行時間就為:當(dāng)前時間加上延遲時間,本質(zhì)上是個時間戳。當(dāng)然,你也可以任意指定時間,這個時間稍后的消息插入中會用到。 代碼很簡單,就是講調(diào)用者傳遞過來的Runnable回調(diào)賦值給message(用處在消息處理中講)。 sendMessageAtTime()和sendMessageAtFrontOfQueue方法都會通過enqueueMessage方法實現(xiàn)消息的入棧:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target \= this;
msg.workSourceUid \= ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
代碼很簡單,主要有以下操作:
- 讓message持有發(fā)送它的Handler的引用(這也是處理消息時能找到對應(yīng)handler的關(guān)鍵)
- 設(shè)置消息是否為異步消息(異步消息無須排隊,通過同步屏障,插隊執(zhí)行)
- 調(diào)用
MessageQueue的enqueueMessage方法將消息加入隊列
消息進入消息隊列
入隊前的準(zhǔn)備工作
enqueueMessage方法是消息加入到MessageQueue的關(guān)鍵,下面分段來分析一下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target \== null) {
throw new IllegalArgumentException("Message must have a target.");
}
//...省略下文代碼
}
這端代碼很簡單:判斷message的target是否為空,為空則拋出異常。其中,target就是上文Handler.enqueueMessage里提到到Handler引用。 接下來下來開始判斷和處理消息
boolean enqueueMessage(Message msg, long when) {
//...省略上文代碼
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e \= new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when \= when;
//...省略下文代碼
}
//...省略下文代碼
}
首先加一個同步鎖,接下來所有的操作都在synchronized代碼塊里運行 然后兩個if語句用來處理兩個異常情況:
- 判斷當(dāng)前msg是否已經(jīng)被使用,若被使用,則排除異常;
- 判斷消息隊列(MessageQueue)是否正在關(guān)閉,如果是,則回收消息,返回入隊失?。╢alse)給調(diào)用者,并打印相關(guān)日志
若一切正常,通過markInUse標(biāo)記消息正在使用(對應(yīng)第一個if的異常),然后設(shè)置消息發(fā)送的時間(機器系統(tǒng)時間)。 接下來開始執(zhí)行插入的相關(guān)操作
將消息加入隊列
繼續(xù)看enqueueMessage的代碼實現(xiàn)
boolean enqueueMessage(Message msg, long when) {
//...省略上文代碼
synchronized (this) {
//...省略上文代碼
//步驟1
Message p \= mMessages;
boolean needWake;
//步驟2
if (p \== null || when \== 0 || when < p.when) {
msg.next \= p;
mMessages \= msg;
needWake \= mBlocked;
} else {
needWake \= mBlocked && p.target \== null && msg.isAsynchronous();
//步驟3
Message prev;
for (;;) {
prev \= p;
p \= p.next;
if (p \== null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake \= false;
}
}
msg.next \= p;
prev.next \= msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
//步驟4
return true;
}
首先說明MessageQueue使用一個單向鏈表維持著消息隊列的,遵循先進先出的軟解。 分析上面這端代碼:
第一步:
mMessages就是表頭,首先取出鏈表頭部。第二步:一個判斷語句,滿足三種條件則直接將msg作為表頭:
- 若表頭為空,說明隊列內(nèi)沒有任何消息,msg直接作為鏈表頭部;
-
when == 0說明消息要立即執(zhí)行(例如sendMessageAtFrontOfQueue方法,但一般的發(fā)送的消息除非特別指定都是發(fā)送時的時間加上延遲時間),msg插入作為鏈表頭部; -
when < p.when,說明要插入的消息執(zhí)行時間早于表頭,msg插入作為鏈表頭部。
第三步:通過循環(huán)不斷的比對隊列中消息的執(zhí)行時間和插入消息的執(zhí)行時間,遵循時間戳小的在前原則,將消息插入和合適的位置。
第四步:返回給調(diào)用者消息插入完成。
需要注意代碼中的needWake和nativeWake,它們是用來喚醒當(dāng)前線程的。因為在消息取出端,當(dāng)前線程會根據(jù)消息隊列的狀態(tài)進入阻塞狀態(tài),在插入時也要根據(jù)情況判斷是否需要喚醒。
接下來就是從消息隊列中取出消息了
從消息隊列里取出消息
依舊是先看看準(zhǔn)備準(zhǔn)備工作
準(zhǔn)備工作
在非主線程中使用Handler,必須要做兩件事
-
Looper.prepare():創(chuàng)建一個Loop -
Looper.loop():開啟循環(huán)
我們先不管它的創(chuàng)建,直接分段看啊循環(huán)開始的代碼:首先是一些檢查和判斷工作,具體細節(jié)在代碼中已注釋
public static void loop() {
//獲取loop對象
final Looper me \= myLooper();
if (me \== null) {
//若loop為空,則拋出異常終止操作
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
//loop循環(huán)重復(fù)開啟
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
//標(biāo)記當(dāng)前l(fā)oop已經(jīng)開啟
me.mInLoop \= true;
//獲取消息隊列
final MessageQueue queue \= me.mQueue;
//確保權(quán)限檢查基于本地進程,
Binder.clearCallingIdentity();
final long ident \= Binder.clearCallingIdentity();
final int thresholdOverride \=
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected \= false;
//...省略下文代碼
}
loop中的操作
接下來就是循環(huán)的正式開啟,精簡關(guān)鍵代碼:
public static void loop() {
//...省略上文代碼
for (;;) {
//步驟一
Message msg \= queue.next();
if (msg \== null) {
//步驟二
return;
}
//...省略非核心代碼
try {
//步驟三
msg.target.dispatchMessage(msg);
//...
} catch (Exception exception) {
//...省略非核心代碼
} finally {
//...省略非核心代碼
}
//步驟四
msg.recycleUnchecked();
}
}
分步驟分析上述代碼:
- 步驟一:從消息隊列
MessageQueue中取出消息(queue.next()可能會造成阻塞,下文會講到) - 步驟二:如果消息為null,則結(jié)束循環(huán)(消息隊列中沒有消息并不會返回null,而是在隊列關(guān)閉才會返回null,下文會講到)
- 步驟三:拿到消息后開始消息的分發(fā)
- 步驟四:回收已經(jīng)分發(fā)了的消息,然后開始新一輪的循環(huán)取數(shù)據(jù)
MessageQueue的next方法
我們先只看第一步消息的取出,其他的在稍后小節(jié)再看,queue.next()代碼較多,依舊分段來看
Message next() {
//步驟一
final long ptr \= mPtr;
if (ptr \== 0) {
return null;
}
//步驟二
int pendingIdleHandlerCount \= \-1;
//步驟三
int nextPollTimeoutMillis \= 0;
//...省略下文代碼
}
- 第一步:如果消息循環(huán)已經(jīng)退出并且已經(jīng)disposed之后,直接返回null,對應(yīng)上文中Loop通過
queue.next()取消息拿到null后退出循環(huán) - 第二部:初始化
IdleHandler計數(shù)器 - 第三部:初始化native需要用的判斷條件,初始值為0,大于0表示還有消息等待處理(延時消息未到執(zhí)行時間),-1則表示沒有消息了。
繼續(xù)分析代碼:
Message next() {
//...省略上文代碼
for(;;){
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
//...省略下文代碼
}
}
這一段比較簡單:
- 開啟一個無限循環(huán)
-
nextPollTimeoutMillis != 0表示消息隊列里沒有消息或者所有消息都沒到執(zhí)行時間,調(diào)用nativeBinder.flushPendingCommands()方法,在進入阻塞之前跟內(nèi)核線程發(fā)送消息,以便內(nèi)核合理調(diào)度分配資源 - 再次調(diào)用native方法,根據(jù)
nextPollTimeoutMillis判斷,當(dāng)為-1時,阻塞當(dāng)前線程(在新消息入隊時會重新進入可運行狀態(tài)),當(dāng)大于0時,說明有延時消息,nextPollTimeoutMillis會作為一個阻塞時間,也就是消息在多就后要執(zhí)行。
繼續(xù)看代碼:
Message next() {
//...省略上文代碼
for(;;){
//...省略上文代碼
//開啟同步鎖
synchronized (this) {
final long now \= SystemClock.uptimeMillis();
//步驟一
Message prevMsg \= null;
Message msg \= mMessages;
//步驟二
if (msg != null && msg.target \== null) {
do {
prevMsg \= msg;
msg \= msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//步驟三
if (msg != null) {
//步驟四
if (now < msg.when) {
nextPollTimeoutMillis \= (int) Math.min(msg.when \- now, Integer.MAX\_VALUE);
} else {
//步驟五
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 {
//步驟六
nextPollTimeoutMillis \= \-1;
}
}
//...省略下文IdleHandler相關(guān)代碼
}
}
分析一下代碼:
- 第一步:獲取到隊列頭部
- 第二步:判斷當(dāng)前消息是否為同步消息(異步消息的target為null),開啟循環(huán)直到發(fā)現(xiàn)同步消息為止
- 第三步:判斷消息是否為null,不為空執(zhí)行第四步,為空執(zhí)行第六步;
- 第四步:判斷消息執(zhí)行的時間,如果大于當(dāng)前時間,給前文提到的
nextPollTimeoutMillis賦新值(當(dāng)前時間和消息執(zhí)行時間的時間差),在這一步基本完成了本次循環(huán)所有的取消息操作,如果當(dāng)前消息沒有到達執(zhí)行時間,本次循環(huán)結(jié)束,新循環(huán)開始,就會使用上文中提到的nativePollOnce(ptr, nextPollTimeoutMillis);方法進入阻塞狀態(tài) - 第五步:從消息隊列中取出需要立即執(zhí)行的消息,結(jié)束整個循環(huán)并返回。
- 第六部:消息隊列中沒有消息,標(biāo)記
nextPollTimeoutMillis,以便下一循環(huán)進入阻塞狀態(tài)
剩下的代碼就基本上是IdleHandler的處理和執(zhí)行了,在IdleHandler小節(jié)里進行講解,這里就不展開說明了。
消息的處理
還記得上文中loop方法中的msg.target.dispatchMessage(msg);嗎? 消息就是通過dispatchMessage方法進行分發(fā)的。其中target是msg所持有的發(fā)送它的handler的引用,它在發(fā)送消息時被賦值。 dispatchMessage的源碼如下:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
代碼很簡單,通過判斷Message是否有Runable來決定是調(diào)用callback還是調(diào)用handleMessage方法,交給你定義的Handler去處理。需要注意的是,callback雖然是一個Runable,但是它并沒有調(diào)用run方法,而是直接執(zhí)行。這說明它并沒有開啟新的線程,就是作為一個方法使用(如果一開始Handler使用kotlin寫的話,此處或許就是一個高階函數(shù)了)。
其他關(guān)鍵點
上面講完了消息處理的主流程,接下來講一下主流程之外的關(guān)鍵點源碼
Loop的創(chuàng)建
還記得上文中的說到的在非主線程中的要調(diào)用**Looper.prepare()**和 **Looper.loop()**方法嗎?這兩個方法可以理解為初始化Loop和開啟loop循環(huán),而主線程中無需這么做是因為在app啟動的main方法中,framework層已經(jīng)幫我們做了。我們分別來看這兩個方法:
static final ThreadLocal<Looper\> sThreadLocal \= new ThreadLocal<Looper\>();
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));
}
private Looper(boolean quitAllowed) {
mQueue \= new MessageQueue(quitAllowed);
mThread \= Thread.currentThread();
}
這里首先使用了一個靜態(tài)的ThreadLocal確保Loop的唯一性,同時做到線程隔離,使得一個線程有且只有一個Loop實例。接著初始化Loop,同時創(chuàng)建MessageQueue (quitAllowed設(shè)置是否允許退出)。在這一步實現(xiàn)了Loop和消息隊列的關(guān)聯(lián)。
需要注意的是,Loop的構(gòu)造方式是私有的,我們只能通過prepare 區(qū)創(chuàng)建,然后通過myLooper方法去獲取。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
ThreadLocal.get源碼:
public T get() {
Thread t \= Thread.currentThread();
ThreadLocalMap map \= getMap(t);
if (map != null) {
ThreadLocalMap.Entry e \= map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result \= (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到,每個Thread都持有一個ThreadLocalMap ,它和HashMap使用相同的數(shù)據(jù)結(jié)構(gòu),使用ThreadLocal作為key值,value就是Loop實例。不難發(fā)現(xiàn):我們只能獲取到當(dāng)前線程的Loop實例。
Loop也提供了主線程中初始化的辦法prepareMainLooper ,但是這個方法明確說明不允許調(diào)用,只能由系統(tǒng)自己調(diào)用。 這基本上就是Loop創(chuàng)建的關(guān)鍵了,也是在這里完成了Loop和消息隊列以及線成之間的關(guān)聯(lián)。
Handler的創(chuàng)建
Handler的構(gòu)造函數(shù)有以下幾個:
- public Handler()
- public Handler(Callback callback)
- public Handler(Looper looper)
- public Handler(Looper looper, Callback callback)
- public Handler(boolean async)
- public Handler(Callback callback, boolean async)
- public Handler(Looper looper, Callback callback, boolean async)
其中第一個和第二個已經(jīng)被廢棄了,實際上第1~5個構(gòu)造方法都是通過調(diào)用public Handler(Callback callback, boolean async)或public Handler(Looper looper, Callback callback, boolean async)實現(xiàn)的,它們的源碼如下:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper \= looper;
mQueue \= looper.mQueue;
mCallback \= callback;
mAsynchronous \= async;
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND\_POTENTIAL\_LEAKS) {
final Class<? extends Handler\> klass \= getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) \== 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper \= Looper.myLooper();
if (mLooper \== null) {
//注意這個異常,loop不能為空的,首先要Looper.prepare();
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue \= mLooper.mQueue;
mCallback \= callback;
mAsynchronous \= async;
}
兩個方法最大的區(qū)別就是一個使用傳遞過來的loop,一個直接使用當(dāng)前線程的loop,然后就是相同的一些初始化操作了。這里就出現(xiàn)了一個關(guān)鍵點handler處理消息所處的線程和創(chuàng)建它的線程無關(guān),而是和創(chuàng)建它時loop的線程有關(guān)的。
這也是Handler能實現(xiàn)線程切換的原因所在: handler的執(zhí)行跟創(chuàng)建handler的線程無關(guān),跟創(chuàng)建looper的線程相關(guān),假如在子線程中創(chuàng)建一個Handler,但是Handler相關(guān)的Looper是主線程的,那么如果handler執(zhí)行post一個runnable,或者sendMessage,最終的handle Message都是在主線程中執(zhí)行的。
Message的創(chuàng)建、回收和復(fù)用機制
我們可以直接使用new關(guān)鍵字去創(chuàng)建一個Message:

這些方法除了形參有些區(qū)別,用來給message不同的成員變量賦值之外,本質(zhì)上都是通過 obtain()來創(chuàng)建Message:
public static final Object sPoolSync \= new Object();
Message next;
private static Message sPool;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m \= sPool;
sPool \= m.next;
m.next \= null;
m.flags \= 0; // clear in-use flag
sPoolSize\--;
return m;
}
}
return new Message();
}
這端代碼很簡單,Message內(nèi)部維持了一個單線鏈表,使用sPool作為頭部,用來存儲Message實體??梢园l(fā)現(xiàn),每次調(diào)用者需要一個新的消息的時候,都會先從鏈表的頭部去取,有消息就直接返回。沒有消息才會創(chuàng)建一個新的消息。
那么這個鏈表是在何時插入消息的呢?接下來看Message的回收:
public static final Object sPoolSync \= new Object();
private static final int MAX\_POOL\_SIZE \= 50;
void recycleUnchecked() {
flags \= FLAG\_IN\_USE;
what \= 0;
arg1 \= 0;
arg2 \= 0;
obj \= null;
replyTo \= null;
sendingUid \= UID\_NONE;
workSourceUid \= UID\_NONE;
when \= 0;
target \= null;
callback \= null;
data \= null;
synchronized (sPoolSync) {
if (sPoolSize < MAX\_POOL\_SIZE) {
next \= sPool;
sPool \= this;
sPoolSize++;
}
}
}
該方法在每次消息從MessageQueue 隊列取出分發(fā)時都會被調(diào)用,就是在上文提到的Loop.loop()方法里。 代碼也很簡單,首先將Message的成員變量還原到初始狀態(tài),然后采用頭插法將回收后的消息插入到鏈表之中(限制了最大容量為50)。而且插入和取出的操作,都是使用的同一把鎖,保證了安全性。
注意插入和取出都是對鏈表的頭部操作,這里和消息隊列里就不太一樣了。雖然都是使用單向鏈表,回收時使用頭插和頭取,先進后出,是個棧。而在MessageQueue里是個隊列,遵循先進先出的原則,而且插入的時候是根據(jù)消息的狀態(tài)確定位置,并沒有固定的插入節(jié)點。
這是一個典型的享元模式,最大的特點就是復(fù)用對象,避免重復(fù)創(chuàng)建導(dǎo)致的內(nèi)存浪費。這也是為什么android官方推薦使用這種方式創(chuàng)建消息的原因:就是為了提高效率減少性能開銷。
IdleHandler
IdleHandler 的定義很簡單,就是一個定義在MessageQueue里的接口:
public static interface IdleHandler {
boolean queueIdle();
}
根據(jù)官方的解釋,在 Looper循環(huán)的過程中,每當(dāng)消息隊列出現(xiàn)空閑:沒有消息或者沒到任何消息的執(zhí)行時間需要滯后執(zhí)行的時候,queueIdle 方法就會被執(zhí)行,而其返回的布爾值標(biāo)識IdleHandler 是永久的還是一次性的:
- ture:永久的,一旦空閑,就會執(zhí)行
- false:一次性的,只有第一次空閑時會執(zhí)行
它的使用方法如下:
Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return true;
}
});
看一下addIdleHandler 的實現(xiàn)
private final ArrayList<IdleHandler\> mIdleHandlers \= new ArrayList<IdleHandler\>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler \== null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
代碼很簡單,就是一個List來保存接口的實現(xiàn)。那么它是怎么實現(xiàn)在出現(xiàn)空閑時調(diào)用呢?
還記得在上文MessageQueue的next方法中省略的代碼嗎?
Message next() {
//...省略不相關(guān)代碼
//步驟一
int pendingIdleHandlerCount \= \-1; // -1 只存在第一次迭代中
for (;;) {
//...省略不相關(guān)代碼
//步驟二
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);
}
//步驟五
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(TAG, "IdleHandler threw exception", t);
}
//步驟七
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//步驟八
pendingIdleHandlerCount \= 0;
}
}
接下來分步驟分析一下代碼:
- 第一步:在取消息的循環(huán)開始前創(chuàng)建局部變量
pendingIdleHandlerCount用來記錄IdleHandler的數(shù)量,只在循環(huán)開始時為-1; - 第二步:當(dāng)沒有取到
Message消息(沒有消息或者沒有可立即執(zhí)行的消息,也沒有進去阻塞狀態(tài))或者消息需要延后執(zhí)行,為pendingIdleHandlerCount賦值記錄IdleHandler的數(shù)量; - 第三步:判斷
IdleHandler的數(shù)量,如果沒有IdleHandler,則直接結(jié)束當(dāng)前循環(huán),并標(biāo)記循環(huán)可進入掛起狀態(tài)。 - 第四步:判斷是否是第一次,初始化
IdleHandler的List - 第五步:開始遍歷所有的
IdleHandler - 第六步:依次執(zhí)行
IdleHandler的queueIdle方法 - 第七部:根據(jù)各
IdleHandler的queueIdle的返回值判斷IdleHandler是永久的還是一次性的,將非永久的從數(shù)組里移除; - 第八步:修改
IdleHandler的數(shù)量信息pendingIdleHandlerCount,避免IdleHandler重復(fù)執(zhí)行。
這就是IdleHandler 的核心原理,它只在消息隊列為空時,或者消息隊列的頭部消息為延時消息時才會被觸發(fā)。當(dāng)消息隊列頭部為延時消息時,它只會觸發(fā)一次哦。在前文中取消息的小節(jié)中我們講過:延時消息在結(jié)束當(dāng)前循環(huán)后進入下一路循環(huán)會觸發(fā)阻塞。
Handler在Framework層的應(yīng)用
不知道你有沒有想過為什么Android在主線程里直接幫你調(diào)用了Looper.prepare()和 Looper.loop()方法,難道只是為了你使用方便嗎?這豈不是有點殺雞用牛刀的感覺?
事實上遠沒有這么簡單,如果你看一下framework的源碼你就會發(fā)現(xiàn),整個android app的運轉(zhuǎn)都是基于Handler進行的。四大組件的運行,它們生命周期也是基于Handler事件模型進行的,以及點擊事件等。這一切均是由Android系統(tǒng)框架層產(chǎn)生相應(yīng)的message再交由一個Handler進行處理的。這個Handler就是ActivityThread內(nèi)部類H,貼一段它的代碼截圖

可以看到,四大組件的生命周期甚至內(nèi)存不足,都有handler在參與。
這也解釋了為什么在主線程執(zhí)行耗時任務(wù)會導(dǎo)致UI卡頓或者ANR:因為所有主線程也就是UI線程的邏輯代碼都是在組件的生命周期里執(zhí)行的,而生命周期又受到Handler的事件體系的控制,當(dāng)在任意生命周期做中執(zhí)行耗時操作,這會導(dǎo)致消息隊列MessageQueue中后面的消息無法得到及時的處理,就會造成延遲,直至視覺上的卡頓,嚴(yán)重的則會進一步觸發(fā)ANR的發(fā)生。