系列目錄: Handler機制原理
MessageQueue介紹
MessageQueue是一個消息隊列吩坝,Handler將Message發(fā)送并加入到消息隊列中凤瘦,消息隊列會按照規(guī)則取出要執(zhí)行的Message。
Java層的MessageQueue負責處理Java的消息拂封,native也有一個MessageQueue負責處理native的消息茬射,我也沒看JNI代碼,此處不做分析了冒签。
代碼分析
1. 成員變量
//用于標示消息隊列是否可以被關(guān)閉在抛,主線程的消息隊列不可關(guān)閉
private final boolean mQuitAllowed;
// 該變量用于保存native代碼中的MessageQueue的指針
private long mPtr;
//在MessageQueue中,所有的Message是以鏈表的形式組織在一起的萧恕,該變量保存了鏈表的第一個元素刚梭,也可以說它就是鏈表的本身
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
//標示MessageQueue是否正在關(guān)閉。
private boolean mQuitting;
// 標示 MessageQueue是否阻塞
private boolean mBlocked;
// 在MessageQueue里面有一個概念叫做障柵票唆,它用于攔截同步的Message朴读,阻止這些消息被執(zhí)行,
// 只有異步Message才會放行走趋。障柵本身也是一個Message衅金,只是它的target為null并且arg1用于區(qū)分不同的障柵,
// 所以該變量就是用于不斷累加生成不同的障柵簿煌。
private int mNextBarrierToken;
2. 構(gòu)造函數(shù)
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
- 設(shè)置MessageQueue是否可以退出
- native層代碼的初始化
3. MessageQueue中的Message分類
- 同步消息
正常情況下我們通過Handler發(fā)送的Message都屬于同步消息氮唯,除非我們在發(fā)送的時候執(zhí)行該消息是一個異步消息。
同步消息會按順序排列在隊列中姨伟,除非指定Message的執(zhí)行時間惩琉,否咋Message會按順序執(zhí)行。
- 同步消息
- 異步消息
想要往消息隊列中發(fā)送異步消息授滓,我們必須在初始化Handler的時候通過構(gòu)造函數(shù)public Handler(boolean async)中指定Handler是異步的琳水,這樣Handler在講Message加入消息隊列的時候就會將Message設(shè)置為異步的肆糕。
- 異步消息
- 障柵
障柵(Barrier) 是一種特殊的Message,它的target為null(只有障柵的target可以為null在孝,如果我們自己視圖設(shè)置Message的target為null的話會報異常)诚啃,并且arg1屬性被用作障柵的標識符來區(qū)別不同的障柵。
障柵的作用是用于攔截隊列中同步消息私沮,放行異步消息始赎。
在道路擁擠的時候會決定哪些車輛可以先通過,這些可以通過的車輛就是異步消息仔燕。
- 障柵
4. enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
//第一步
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 第二步
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 第三步
synchronized (this) {
// 第四步
//判斷消息隊列是否正在關(guān)閉
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;
Message p = mMessages;
boolean needWake;
// 第六步
//根據(jù)when的比較來判斷要添加的Message是否應(yīng)該放在隊列頭部造垛,當?shù)谝粋€添加消息的時候,
// 測試隊列為空晰搀,所以該Message也應(yīng)該位于頭部五辽。
if (p == null || when == 0 || when < p.when) {
// 把msg的下一個元素設(shè)置為p
msg.next = p;
// 把msg設(shè)置為鏈表的頭部元素
mMessages = msg;
// 如果有阻塞,則需要喚醒
needWake = mBlocked;
} else {
// 第七步
//除非消息隊列的頭部是障柵(barrier)外恕,或者消息隊列的第一個消息是異步消息杆逗,
//否則如果是插入到中間位置,我們通常不喚醒消息隊列鳞疲,
// 第八步
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 第九步
// 不斷遍歷消息隊列罪郊,根據(jù)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;
}
// 第十三步
if (needWake) {
nativeWake(mPtr);
}
}
// 第十四步
return true;
}
第1步驟尚洽、 判斷msg的target變量是否為null悔橄,如果為null,則為障柵(barrier)腺毫,而障柵(barrier)入隊則是通過postSyncBarrier()方法入隊癣疟,所以msg的target一定有值
第2步驟、 判斷msg的標志位拴曲,因為此時的msg應(yīng)該是要入隊争舞,意味著msg的標志位應(yīng)該顯示還未被使用。如果顯示已使用澈灼,明顯有問題,直接拋異常店溢。
第3步驟叁熔、 加入同步鎖。
第4步驟床牧、 判斷消息隊列是否正在被關(guān)閉荣回,如果是正在被關(guān)閉,則return false告訴消息入隊是失敗戈咳,并且回收消息
第5步驟心软、 設(shè)置msg的when并且修改msg的標志位壕吹,msg標志位顯示為已使用
第6步驟、 如果p==null則說明消息隊列中的鏈表的頭部元素為null删铃;when == 0 表示立即執(zhí)行耳贬;when< p.when 表示 msg的執(zhí)行時間早與鏈表中的頭部元素的時間,所以上面三個條件猎唁,那個條件成立咒劲,都要把msg設(shè)置成消息隊列中鏈表的頭部是元素
第7步驟、 如果上面三個條件都不滿足則說明要把msg插入到中間的位置诫隅,不需要插入到頭部
第8步驟腐魂、 如果頭部元素不是障柵(barrier)或者異步消息,而且還是插入中間的位置逐纬,我們是不喚醒消息隊列的蛔屹。
第9步驟、 進入一個死循環(huán)豁生,將p的值賦值給prev兔毒,前面的帶我們知道,p指向的是mMessage沛硅,所以這里是將prev指向了mMessage眼刃,在下一次循環(huán)的時候,prev則指向了第一個message摇肌,一次類推擂红。接著講p指向了p.next也就是mMessage.next,也就是消息隊列鏈表中的第二個元素围小。這一步驟實現(xiàn)了消息指針的移動昵骤,此時p表示的消息隊列中第二個元素。
第10步驟肯适、 p==null变秦,則說明沒有下一個元素,即消息隊列到頭了框舔,跳出循環(huán)蹦玫;p!=null&&when < p.when 則說明當前需要入隊的這個message的執(zhí)行時間是小于隊列中這個任務(wù)的執(zhí)行時間的,也就是說這個需要入隊的message需要比隊列中這個message先執(zhí)行刘绣,則說明這個位置剛剛是適合這個message的樱溉,所以跳出循環(huán)。 如果上面的兩個條件都不滿足纬凤,則說明這個位置還不是放置這個需要入隊的message福贞,則繼續(xù)跟鏈表中后面的元素,也就是繼續(xù)跟消息隊列中的下一個消息進行對比停士,直到滿足條件或者到達隊列的末尾挖帘。
第11步驟完丽、 因為沒有滿足條件,說明隊列中還有消息拇舀,不需要喚醒逻族。
第12步驟、 跳出循環(huán)后主要做了兩件事:事件A你稚,將入隊的這個消息的next指向循環(huán)中獲取到的應(yīng)該排在這個消息之后message瓷耙。事件B,將msg前面的message.next指向了msg刁赖。這樣就將一個message完成了入隊搁痛。
第13步驟、 如果需要喚醒宇弛,則喚醒鸡典,具體請看后面的Handler中的Native詳解。
第14步驟枪芒、 返回true彻况,告知入隊成功
5. next()
Message next() {
// 如果消息循環(huán)已經(jīng)退出了。則直接在這里return舅踪。因為調(diào)用disposed()方法后mPtr=0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//記錄空閑時處理的IdlerHandler的數(shù)量
int pendingIdleHandlerCount = -1;
// native層用到的變量 纽甘,如果消息尚未到達處理時間,則表示為距離該消息處理事件的總時長抽碌,
// 表明Native Looper只需要block到消息需要處理的時間就行了悍赢。 所以nextPollTimeoutMillis>0表示還有消息待處理
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
//刷新下Binder命令,一般在阻塞前調(diào)用
Binder.flushPendingCommands();
}
// 調(diào)用native層進行消息標示货徙,nextPollTimeoutMillis 為0立即返回左权,為-1則阻塞等待。
nativePollOnce(ptr, nextPollTimeoutMillis);
//加上同步鎖
synchronized (this) {
// 獲取開機到現(xiàn)在的時間
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// 獲取MessageQueue的鏈表表頭的第一個元素
Message msg = mMessages;
// 判斷Message是否是障柵痴颊,如果是則執(zhí)行循環(huán)赏迟,攔截所有同步消息,直到取到第一個異步消息為止
if (msg != null && msg.target == null) {
// 如果能進入這個if蠢棱,則表面MessageQueue的第一個元素就是障柵(barrier)
// 循環(huán)遍歷出第一個異步消息锌杀,這段代碼可以看出障柵會攔截所有同步消息
do {
prevMsg = msg;
msg = msg.next;
//如果msg==null或者msg是異步消息則退出循環(huán),msg==null則意味著已經(jīng)循環(huán)結(jié)束
} while (msg != null && !msg.isAsynchronous());
}
// 判斷是否有可執(zhí)行的Message
if (msg != null) {
// 判斷該Mesage是否到了被執(zhí)行的時間泻仙。
if (now < msg.when) {
// 當Message還沒有到被執(zhí)行時間的時候抛丽,記錄下一次要執(zhí)行的Message的時間點
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Message的被執(zhí)行時間已到
// 從隊列中取出該Message,并重新構(gòu)建原來隊列的鏈接
// 刺客說明說有消息饰豺,所以不能阻塞
mBlocked = false;
// 如果還有上一個元素
if (prevMsg != null) {
//上一個元素的next(越過自己)直接指向下一個元素
prevMsg.next = msg.next;
} else {
//如果沒有上一個元素,則說明是消息隊列中的頭元素允蜈,直接讓第二個元素變成頭元素
mMessages = msg.next;
}
// 因為要取出msg冤吨,所以msg的next不能指向鏈表的任何元素蒿柳,所以next要置為null
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
// 標記該Message為正處于使用狀態(tài),然后返回Message
msg.markInUse();
return msg;
}
} else {
// 沒有任何可執(zhí)行的Message漩蟆,重置時間
nextPollTimeoutMillis = -1;
}
// 關(guān)閉消息隊列垒探,返回null,通知Looper停止循環(huán)
if (mQuitting) {
dispose();
return null;
}
// 當?shù)谝淮窝h(huán)的時候才會在空閑的時候去執(zhí)行IdleHandler怠李,從代碼可以看出所謂的空閑狀態(tài)
// 指的就是當隊列中沒有任何可執(zhí)行的Message圾叼,這里的可執(zhí)行有兩要求,
// 即該Message不會被障柵攔截捺癞,且Message.when到達了執(zhí)行時間點
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 這里是消息隊列阻塞( 死循環(huán)) 的重點夷蚊,消息隊列在阻塞的標示是消息隊列中沒有任何消息,
// 并且所有的 IdleHandler 都已經(jīng)執(zhí)行過一次了
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// 初始化要被執(zhí)行的IdleHandler髓介,最少4個
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 開始循環(huán)執(zhí)行所有的IdleHandler惕鼓,并且根據(jù)返回值判斷是否保留IdleHandler
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);
}
}
}
// 重點代碼,IdleHandler只會在消息隊列阻塞之前執(zhí)行一次唐础,執(zhí)行之后改標示設(shè)置為0箱歧,
// 之后就不會再執(zhí)行,一直到下一次調(diào)用MessageQueue.next() 方法一膨。
pendingIdleHandlerCount = 0;
// 當執(zhí)行了IdleHandler 的 處理之后呀邢,會消耗一段時間,這時候消息隊列里的可能有消息已經(jīng)到達
// 可執(zhí)行時間豹绪,所以重置該變量回去重新檢查消息隊列价淌。
nextPollTimeoutMillis = 0;
}
}
首先、MessageQueue會先判斷隊列中是否有障柵的存在森篷,如果有的話输钩,只會返回異步消息,否則就逐個返回仲智。
其次买乃、當MessageQueue沒有任何消息可以處理的時候,它會進度阻塞狀態(tài)等待新的消息到來(無線循環(huán))钓辆,在阻塞之前它會執(zhí)行以便 IdleHandler剪验,所謂的阻塞其實就是不斷的循環(huán)查看是否有新的消息進入隊列中。
再次前联、當MessageQueue被關(guān)閉的時候功戚,其成員變量mQuitting會被標記為true,然后在Looper視圖從隊列中取出Message的時候返回null似嗤,而Message==null就是告訴Looper消息隊列已經(jīng)關(guān)閉啸臀,應(yīng)該停止循環(huán)了,這一點可以在Looper.loop()方法源碼中看出。
最后乘粒、如果大家細心一定會發(fā)現(xiàn)豌注,Handler線程里面實際上有兩個無線循環(huán)體,Looper循環(huán)體和MessageQueue循環(huán)體灯萍,真正阻塞的地方是MessageQueue的next()方法里轧铁。
MessageQueue的核心代碼就是enqueueMessage()和next(),還有就是一些概念性的理解。
最重要的一點是要知道兩個for循環(huán)是在哪里阻塞的:
Looper循環(huán)體和MessageQueue循環(huán)體旦棉,真正阻塞的地方是MessageQueue的next()方法里.