Android 消息機制深入源碼分析 [ 一 ]
Android 消息機制之 ThreadLocal 深入源碼分析 [ 二 ]
Android 消息機制之 Looper 深入源碼分析 [ 三 ]
Android 消息機制之 Message 與消息對象池的深入源碼分析 [ 四 ]
Android 消息機制之 MessageQueue 深入源碼分析 [ 五 ]
Android 消息機制之初識Handler [ 六 ]
Android 消息機制之 Handler 發(fā)送消息的深入源碼分析 [ 七 ]
Android 消息機制之 MessageQueue.next() 消息取出的深入源碼分析 [ 八 ]
Android 消息機制之消息的其他處理深入源碼分析 [ 九 ]
Android 消息機制總結 [ 十 ]
緊接上一章消息的發(fā)送, 本章內容為消息的取出分析學習.
消息的取出主要是通過 Looper
的 loop
方法. 這個方法在第三章的時候已經分析過, 分為以下幾步
- 獲取
Looper
對象 - 獲取
MessageQueue
消息隊列對象 - 死循環(huán)遍歷
- 通過
queue.next()
來從MessageQueue
的消息隊列中獲取一個Message msg
對象 - 通過
msg.target
,dispatchMessage(msg)
來處理消息 - 通過
msg.recycleUnchecked()
方法來回收Message
到消息對象池中.
其中 Message.recycleUnchecked()
在第四章的時候已經分析過, 那么現(xiàn)在就剩下 MessageQueue.next()
與 handler.dispatchMessage()
. 那么先來看 MessageQueue.next()
1. MessageQueue.next()
MessageQueue.java 310 行, 代碼過多, 將分段分析.
Message next() {
//分析 1
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//分析 2
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//分析 3
int nextPollTimeoutMillis = 0;
- 分析:
- 如果消息循環(huán)已經退出了, 則在這里直接
return
, 因為調用了disposed()
方法后,mPtr = 0
;
- 記錄空閑時間處理的 IdleHandler 的數(shù)量. 初始為 -1
native
需要用到的變量. 初始化為 0, 如果大于 0, 表示還有消息待處理(未到執(zhí)行時間). -1表示阻塞等待
//分析 4
for (;;) {
//分析 5
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//分析 6
nativePollOnce(ptr, nextPollTimeoutMillis);
//分析 7
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//分析 8
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
- 分析
- 開啟死循環(huán) (循環(huán)內容一直到最后)
- 如果還有消息未處理, 就刷新
Binder
命令, 一般在阻塞前調用
- 調用
native
方法, 當nextPollTimeoutMillis == -1
的時候就阻塞等待, 直到下一條消息可用為止. 否則就繼續(xù)向下執(zhí)行. 還記得第七章發(fā)送消息時候消息入隊操作的最后嗎? 里面有一個nativeWake()
喚醒. 就是喚醒此處. 沒有消息的時候, 這里就處于阻塞狀態(tài). 當我們發(fā)送消息的時候, 這里就會被喚醒.
- 加上同步鎖, 然后獲取從開機到現(xiàn)在的時間, 獲取消息鏈表頭部元素,
- 判斷第一個消息是不是障柵. (在前面第五篇中說過: 只有障柵的
tatget
才為null
), 如果第一個消息是障柵, 則又開啟一個循環(huán), 取出第一個異步消息, 從do..while
這段代碼中. 可以印證出障柵會攔截所有的同步消息.
如果msg != null && ! msg.isAsynchronous()
這個條件成立, 說明就是同步消息, 那么就跳出同步消息繼續(xù)循環(huán), 直到找到第一條異步消息并賦值給
.就退出do..while
循環(huán).
//分析 9
if (msg != null) {
//分析 10
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { //分析 11
mBlocked = false;
//分析 12
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 {
//分析 13
nextPollTimeoutMillis = -1;
}
- 分析
- 判斷消息是否為
null
如果進入了分析 8 中的if
邏輯, 那么到這一步,msg
要么是一個異步消息, 要么為null
. 不可能是同步消息
如果沒有進入分析 8, 說明頭部消息不是障柵, 需要判斷是否是可執(zhí)行的異步消息或者同步消息.
- 如果當前時間小于消息的執(zhí)行時間, 表示當前可執(zhí)行的消息還未到執(zhí)行時間, 則記錄下剩余時間.
- 如果當前時間大于等于消息的執(zhí)行時間, 表示當前消息的執(zhí)行時間已經到了, 接著將
MessageQueue.mBlocked
設置為false
表示MessageQueue
不阻塞,mBlocked
變量與消息入隊時,需要不需要喚醒
- 這個
if..esle..
判斷內的邏輯就是將需要立刻執(zhí)行的消息從消息隊列中抽出來, 然后再將消息隊列組合起來. 再將要執(zhí)行消息的next
賦值為null
,并標記為正在使用. 最后把要執(zhí)行的消息返回出去. 獲取消息結束.
例如: 消息鏈表中有三個消息 A -> B -> C, A是障柵, B是異步, C是同步. 分析 12 走完, 就變成了, B 是單獨的一個消息, 并將 B.next 置為 null, 最后組合后的消息鏈表就為 A -> C.
- 如果分析9 的判斷不成立, 則表示目前沒有可執(zhí)行的消息, 設置
nextPollTimeoutMillis = -1
. 在分析 3 中說過這個變量的作用表示是否阻塞.
//分析 14
if (mQuitting) {
dispose();
return null;
}
//分析 15
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//分析 16
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
//分析 17
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
注意: 當代碼開始執(zhí)行這里的時候, msg
要么為 null
, 要么未到執(zhí)行時間.
- 分析
- 官方翻譯為:現(xiàn)在已處理所有掛起的消息,請?zhí)幚硗顺鱿⒑戎汀P枰P閉消息隊列, 返回
null
, 通知Looper
停止循環(huán).
- 當?shù)谝淮窝h(huán)才會在空閑的時候去執(zhí)行
IdleHanler
, 從代碼可以看出所謂的空閑狀態(tài)指的就是, 目前沒有任何可執(zhí)行的Message
, 這里的可執(zhí)行有兩個要求, 當前Message
不會被障柵攔截, 當前Message
到達了執(zhí)行時間. 才會為變量pendingIdleHandlerCount
賦值.
- 如果沒有在空閑時需要執(zhí)行的
IdleHandler
. 這里是消息隊列阻塞(死循環(huán))的重點, 在msg = null
或者未到執(zhí)行時間的情況下, 表示消息隊列空閑, 但是也沒有可執(zhí)行的idleHandler
, 那么就把mBlock
變量置為true
, 表示需要喚醒, 并開始下一次循環(huán). 就會回到上面的分析 5 和分析 6, 這個時候nextPollTimeoutMillis
要么為 -1, 要么就為上個消息剩下要執(zhí)行的時間. 那么分析 5 肯定成立, 接著刷新binder
命令, 然后在分析 6 中就開始阻塞, 只要不是 0, 就會阻塞. 等要執(zhí)行的時間到了就會被喚醒. 或者當有新的消息入隊的時候. 就會根據(jù)mBlock
的值來判斷是否要喚醒消息隊列.
- 如果有需要在空閑時執(zhí)行的
IdleHandler
, 接著判斷是否初始化過mPendingIdleHandlers
數(shù)組, 最小4 個長度. 并把要執(zhí)行的IdleHandler
賦值給mPendingIdleHandlers
數(shù)組.
//分析18
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);
}
}
}
//分析 19
pendingIdleHandlerCount = 0;
//分析 20
nextPollTimeoutMillis = 0;
}
}
注意: 執(zhí)行到這里, 說明目前沒有可執(zhí)行的 Message
, 但是有可以在空閑時執(zhí)行的 IdleHandler
.
官方對這個循環(huán)的注釋為:
- 執(zhí)行
idleHandler
. 我們只會在第一次迭代時到達此代碼塊影涉。為什么呢, 這個稍后分析, 先分析完 19 和 20.
IdHandler
只會在消息隊列阻塞之前執(zhí)行一次, 執(zhí)行之后,pendingIdleHandlerCount
賦值為 0, 之后就不會再執(zhí)行. 一直到下一次調用MessageQueue.next()
方法.
- 當執(zhí)行了
IdleHander
之后, 會消耗一段時間, 這時候消息隊列里可能已經有消息到達可執(zhí)行時間, 所以重置nextPollTimeoutMillis
回去重新檢查消息隊列.
?
關于分析 18 的疑問. 為什么只會在第一次循環(huán)的時候會執(zhí)行這里呢.
也就是說只有在
MessageQueue.next
方法的死循環(huán), 第一次循環(huán)的時候,msg
為null
或者msg
未到執(zhí)行時間, 并且有可執(zhí)行的空閑IdleHandler
的情況下會 執(zhí)行. 或者下次調用MessageQueue.next()
方法. 為什么呢? 一起來分析一下,
第一次循環(huán)開始,
- 分析 6 那里肯定不會阻塞, 因為
nextPollTimeoutMillis
初始值為 0.- 然后到分析 9 , 如果
msg != null
并且有需要立即執(zhí)行的消息的話, 就直接跳出死循環(huán)了, 我們假設msg
為null
或者msg
未到執(zhí)行時間. 那么在分析 9 內, 會對nextPollTimeoutMillis
賦值,- 接著到分析 15,
pendingIdleHandlerCount
初始值為 -1, 判斷成立, 給pendingIdleHandlerCount
賦值- 到分析 16, 條件不成立, (因為我們假設有可執(zhí)行的空閑
IdleHandler
). 如果沒有則直接就進行下次一次循環(huán)了, 下一次循環(huán)到分析 6 處, 就會阻塞了.- 分析 18, 開始執(zhí)行
IdleHandler
. 執(zhí)行一個, 就從mIdleHandlers
中移除一個.- 分析 19,
pendingIdleHandlerCount
和nextPollTimeoutMillis
都賦值為 0,第二次循環(huán)開始
- 分析 6 不阻塞, 因為在第一次循環(huán)的分析 20 處被重置了, 需要重新檢查消息隊列.
- 分析 9 如果有消息到執(zhí)行時間了, 會直接
return
, 沒有消息或者還是未到時間就再對nextPollTimeoutMillis
賦值 -1 或者剩余執(zhí)行時間. 接著向下走.- 分析 15. 這里的判斷就不會成立了, 因為在第一次循環(huán)最后分析 19 處
pendingIdleHandlerCount
被置為 0 了. 所以跳到分析 16.- 分析 16.
pendingIdleHandlerCount <= 0
條件成立. 跳出本次循環(huán), 開始進入第三次循環(huán)了. 然后在第三次循環(huán)中的分析 6, 就開始阻塞. 直到被喚醒.
?
總結
總的來說當在 Looper.loop()
方法的死循環(huán)內, 調用MessageQueue.next()
方法獲取一個 Message
的時候, 大致會分為以下幾步.
MessageQueue
會先判斷隊列中是否有障柵存在
- 有: 返回第一個異步消息,
- 沒有: 逐個返回同步消息
- 當
MessageQueue
中沒有任何消息可以處理或者未到消息的執(zhí)行時間的時候, 就會進入阻塞狀態(tài)等待新的消息到來被喚醒. 或者有消息的執(zhí)行時間到了被喚醒. 在阻塞之前會執(zhí)行一次IdleHandler
.- 當
MessageQueue
被關閉的時候, 成員變量mQutting
會被標記為true
, 然后在Looper
試圖從MessageQueue
取消息的時候返回null
. 而Message = null
就是告訴Looper
消息隊列已經關閉, 應該停止死循環(huán)了.(在第三篇Looper.loop()
方法分析中有說明 )Handler
中實際上有兩個無限循環(huán)體, 一個是在Looper.loop()
中的循環(huán)體, 以及MessageQueue
中的循環(huán)體. 真正的阻塞是在MessageQueue
的循環(huán)體中.
好了, 本章分析學習就到這里結束了, 下一章將會分析學習 消息分發(fā)處理, 消息的移除, 以及消息的其他操作.