在上一篇文章中我們分析了Handler 胧弛、Looper员凝、 MessageQueue 摇锋、線程之間的關(guān)系苇侵,簡單的說就是:一個(gè)線程綁定一個(gè)Looper油狂,一個(gè)Looper維護(hù)一個(gè)MessageQueue隊(duì)列历恐,而一個(gè)線程可以對應(yīng)多個(gè)Handler。而在Handler的消息機(jī)制中专筷,MessageQueue可能算是最重要的弱贼,今天我們就來分析這個(gè)類。
在分析之前磷蛹,先提出兩個(gè)問題:
1.Handler.sendMessageDelayed()怎么實(shí)現(xiàn)延遲的吮旅?
2.Looper.loop是一個(gè)死循環(huán),拿不到需要處理的Message就會(huì)阻塞味咳,那在UI線程中為什么不會(huì)導(dǎo)致ANR庇勃?
現(xiàn)在,我們帶著這兩個(gè)問題進(jìn)入MessageQueue的分析中槽驶。首先看第一個(gè)责嚷,Handler.sendMessageDelayed()的源碼如下:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看出 : 消息被處理的時(shí)間 = 當(dāng)前時(shí)間+延遲的時(shí)間
至于這個(gè)地方為什么要用SystemClock.uptimeMillis() 而不用SystemClock. currentTimeMillis(),這里可以看兩者的區(qū)別(摘自網(wǎng)上):
System.currentTimeMillis() 方法產(chǎn)生一個(gè)標(biāo)準(zhǔn)的自1970年1月1號0時(shí)0分0秒所差的毫秒數(shù)掂铐。該時(shí)間可以通過調(diào)用setCurrentTimeMillis(long)方法來手動(dòng)設(shè)置罕拂,也可以通過網(wǎng)絡(luò)來自動(dòng)獲取。這個(gè)方法得到的毫秒數(shù)為“1970年1月1號0時(shí)0分0秒 到 當(dāng)前手機(jī)系統(tǒng)的時(shí)間”的差堡纬。因此如果在執(zhí)行時(shí)間間隔的值期間用戶更改了手機(jī)系統(tǒng)的時(shí)間聂受,那么得到的結(jié)果是不可預(yù)料的蒿秦。因此它不適合用在需要時(shí)間間隔的地方烤镐,如Thread.sleep, Object.wait等,因?yàn)樗闹悼赡軙?huì)被改變棍鳖。
SystemClock.uptimeMillis()方法用來計(jì)算自開機(jī)啟動(dòng)到目前的毫秒數(shù)炮叶。如果系統(tǒng)進(jìn)入了深度睡眠狀態(tài)(CPU停止運(yùn)行碗旅、顯示器息屏、等待外部輸入設(shè)備)該時(shí)鐘會(huì)停止計(jì)時(shí)镜悉,但是該方法并不會(huì)受時(shí)鐘刻度祟辟、時(shí)鐘閑置時(shí)間亦或其它節(jié)能機(jī)制的影響。因此SystemClock.uptimeMillis()方法也成為了計(jì)算間隔的基本依據(jù)侣肄,比如Thread.sleep()旧困、Object.wait()、System.nanoTime()以及Handler都是用SystemClock.uptimeMillis()方法稼锅。這個(gè)時(shí)鐘是保證單調(diào)性,適用于計(jì)算不跨越設(shè)備的時(shí)間間隔吼具。
Handler.sendMessageDelayed()方法最終會(huì)調(diào)用enqueueMessage方法進(jìn)入MessageQueue的enqueueMessage方法中:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue中最重要的就是兩個(gè)方法:
1.enqueueMessage向隊(duì)列中插入消息
2.next 從隊(duì)列中取出消息
先分析enqueueMessage:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//msg.target就是發(fā)送此消息的Handler
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {//表示此消息正在被使用
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {//表示此消息隊(duì)列已經(jīng)被放棄了
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;//將延遲時(shí)間封裝到msg內(nèi)部
Message p = mMessages;//消息隊(duì)列的第一個(gè)元素
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果此隊(duì)列中頭部元素是null(空的隊(duì)列,一般是第一次)矩距,或者此消息不是延時(shí)的消息拗盒,則此消息需要被立即處理,此時(shí)會(huì)將這個(gè)消息作為新的頭部元素锥债,并將此消息的next指向舊的頭部元素陡蝇,然后判斷如果Looper獲取消息的線程如果是阻塞狀態(tài)則喚醒它,讓它立刻去拿消息處理
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//如果此消息是延時(shí)的消息哮肚,則將其添加到隊(duì)列中登夫,原理就是鏈表的添加新元素,按照when绽左,也就是延遲的時(shí)間來插入的悼嫉,延遲的時(shí)間越長,越靠后拼窥,這樣就得到一條有序的延時(shí)消息鏈表戏蔑,取出消息的時(shí)候,延遲時(shí)間越小的鲁纠,就被先獲取了总棵。插入延時(shí)消息不需要喚醒Looper線程。
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
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.
if (needWake) {//喚醒線程
nativeWake(mPtr);
}
}
return true;
}
源碼中主要的地方我給了注釋改含,可以參考參考情龄。
由此可以看出:
MessageQueue中enqueueMessage方法的目的有兩個(gè):
1.插入消息到消息隊(duì)列
2.喚醒Looper中等待的線程(如果是及時(shí)消息并且線程是阻塞狀態(tài))
同時(shí)我們知道了MessageQueue的底層數(shù)據(jù)結(jié)構(gòu)是單向鏈表,MessageQueue中的成員變量mMessages指向的就是該鏈表的頭部元素捍壤。
接下來我們再來分析一下取出消息的方法next():
next()方法代碼比較多骤视,下面是主要部分,后面省略了一部分IdleHandler的處理邏輯鹃觉,用于空閑的時(shí)候處理不緊急事件用的专酗,有興趣的自行分析。
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
//從注釋可以看出盗扇,只有l(wèi)ooper被放棄的時(shí)候(調(diào)用了quit方法)才返回null祷肯,mPtr是MessageQueue的一個(gè)long型成員變量沉填,關(guān)聯(lián)的是一個(gè)在C++層的MessageQueue,阻塞操作就是通過底層的這個(gè)MessageQueue來操作的佑笋;當(dāng)隊(duì)列被放棄的時(shí)候其變?yōu)?翼闹。
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞方法,主要是通過native層的epoll監(jiān)聽文件描述符的寫入事件來實(shí)現(xiàn)的蒋纬。
//如果nextPollTimeoutMillis=-1猎荠,一直阻塞不會(huì)超時(shí)。
//如果nextPollTimeoutMillis=0蜀备,不會(huì)阻塞法牲,立即返回。
//如果nextPollTimeoutMillis>0琼掠,最長阻塞nextPollTimeoutMillis毫秒(超時(shí))拒垃,如果期間有程序喚醒會(huì)立即返回。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//msg.target == null表示此消息為消息屏障(通過postSyncBarrier方法發(fā)送來的)
//如果發(fā)現(xiàn)了一個(gè)消息屏障瓷蛙,會(huì)循環(huán)找出第一個(gè)異步消息(如果有異步消息的話)悼瓮,所有同步消息都將忽略(平常發(fā)送的一般都是同步消息)
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果消息此刻還沒有到時(shí)間,設(shè)置一下阻塞時(shí)間nextPollTimeoutMillis艰猬,進(jìn)入下次循環(huán)的時(shí)候會(huì)調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)進(jìn)行阻塞横堡;
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//正常取出消息
//設(shè)置mBlocked = false代表目前沒有阻塞
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
//沒有消息,會(huì)一直阻塞冠桃,直到被喚醒
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
由此可以看出:
1.當(dāng)首次進(jìn)入或所有消息隊(duì)列已經(jīng)處理完成命贴,由于此刻隊(duì)列中沒有消息(mMessages為null),這時(shí)nextPollTimeoutMillis = -1 食听,然后會(huì)處理一些不緊急的任務(wù)(IdleHandler)胸蛛,之后線程會(huì)一直阻塞,直到被主動(dòng)喚醒(插入消息后根據(jù)消息類型決定是否需要喚醒)樱报。
2.讀取列表中的消息葬项,如果發(fā)現(xiàn)消息屏障,則跳過后面的同步消息迹蛤。
3.如果拿到的消息還沒有到時(shí)間民珍,則重新賦值nextPollTimeoutMillis = 延時(shí)的時(shí)間,線程會(huì)阻塞盗飒,直到時(shí)間到后自動(dòng)喚醒
4.如果消息是及時(shí)消息或延時(shí)消息的時(shí)間到了嚷量,則會(huì)返回此消息給looper處理。
通過enqueueMessage和next兩個(gè)方法的分析我們不難得出:
消息的入列和出列是一個(gè)生產(chǎn)-消費(fèi)者模式逆趣,Looper.loop()在一個(gè)線程中調(diào)用next()不斷的取出消息蝶溶,另外一個(gè)線程則通過enqueueMessage向隊(duì)列中插入消息,所以在這兩個(gè)方法中使用了synchronized (this) {}同步機(jī)制汗贫,其中this為MessageQueue對象身坐,不管在哪個(gè)線程,這個(gè)對象都是同一個(gè)落包,因?yàn)镠andler中的mQueue指向的是Looper中的mQueue部蛇,這樣防止了多個(gè)線程對同一個(gè)隊(duì)列的同時(shí)操作。
現(xiàn)在咐蝇,我們對開篇的第一個(gè)問題做個(gè)回答:
Handler.sendMessageDelayed()怎么實(shí)現(xiàn)延遲的涯鲁?
前面我們分析了如果拿到的消息還沒有到時(shí)間,則會(huì)重新設(shè)置超時(shí)時(shí)間并賦值給nextPollTimeoutMillis有序,然后調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)進(jìn)行阻塞抹腿,這是一個(gè)本地方法,會(huì)調(diào)用底層C++代碼旭寿,C++代碼最終會(huì)通過Linux的epoll監(jiān)聽文件描述符的寫入事件來實(shí)現(xiàn)延遲的警绩。
對于第二個(gè)問題:
Looper.loop是一個(gè)死循環(huán),拿不到需要處理的Message就會(huì)阻塞盅称,那在UI線程中為什么不會(huì)導(dǎo)致ANR肩祥?
首先我們來看造成ANR的原因:
1.當(dāng)前的事件沒有機(jī)會(huì)得到處理(即主線程正在處理前一個(gè)事件,沒有及時(shí)的完成或者looper被某種原因阻塞住了)
2.當(dāng)前的事件正在處理缩膝,但沒有及時(shí)完成
我們再來看一下APP的入口ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
顯而易見的混狠,如果main方法中沒有l(wèi)ooper進(jìn)行死循環(huán),那么主線程一運(yùn)行完畢就會(huì)退出疾层,會(huì)導(dǎo)致直接崩潰将饺,還玩什么!
現(xiàn)在我們知道了消息循環(huán)的必要性痛黎,那為什么這個(gè)死循環(huán)不會(huì)造成ANR異常呢予弧?
我們知道Android 的是由事件驅(qū)動(dòng)的,looper.loop() 不斷地接收事件湖饱、處理事件桌肴,每一個(gè)點(diǎn)擊觸摸或者說Activity的生命周期都是運(yùn)行在 Looper的控制之下,如果它停止了琉历,應(yīng)用也就停止了坠七。只能是某一個(gè)消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它旗笔,這也就是我們?yōu)槭裁床荒茉赨I線程中處理耗時(shí)操作的原因彪置。
主線程Looper從消息隊(duì)列讀取消息,當(dāng)讀完所有消息時(shí)蝇恶,主線程阻塞拳魁。子線程往消息隊(duì)列發(fā)送消息,喚醒主線程撮弧,主線程被喚醒只是為了讀取消息潘懊,當(dāng)消息讀取完畢姚糊,再次睡眠。因此loop的循環(huán)并不會(huì)對CPU性能有過多的消耗授舟。