收錄:
Android Handler機(jī)制 - MessageQueue如何處理消息
Handler 如何做到阻塞
Android篇:2019初中級Android開發(fā)社招面試解答(中)
Handler消息機(jī)制組成:
- Message(消息):需要被傳遞的消息,消息分為硬件產(chǎn)生的消息(如按鈕进胯、觸摸)和軟件生成的消息。
- MessageQueue(消息隊列):負(fù)責(zé)消息的存儲與管理胁镐,負(fù)責(zé)管理由 Handler發(fā)送過來的Message。讀取后會自動刪除消息盯漂,單鏈表維護(hù),插入和刪除上有優(yōu)勢就缆。在其next()方法中會無限循環(huán),不斷判斷是否有消息竭宰,有就返回這條消息并移除。
- Handler(消息處理器):負(fù)責(zé)Message的發(fā)送及處理切揭。主要向消息池發(fā)送各種消息事件(Handler.sendMessage())和處理相應(yīng)消息事件(Handler.handleMessage()),按照先進(jìn)先出執(zhí)行伴箩,內(nèi)部使用的是單鏈表的結(jié)構(gòu)。
- Looper(消息池/循環(huán)機(jī)制):負(fù)責(zé)關(guān)聯(lián)線程以及消息的分發(fā)嗤谚,在該線程下從 MessageQueue獲取 Message,分發(fā)給Handler巩步,Looper創(chuàng)建的時候會創(chuàng)建一個 MessageQueue,調(diào)用loop()方法的時候消息循環(huán)開始椅野,其中會不斷調(diào)用messageQueue的next()方法,當(dāng)有消息就處理竟闪,否則阻塞在messageQueue的next()方法中离福。當(dāng)Looper的quit()被調(diào)用的時候會調(diào)用messageQueue的quit()炼蛤,此時next()會返回null,然后loop()方法也就跟著退出理朋。
如何保證looper的唯一性
每個線程只有一個looper绿聘,而每個Thread中都又一個關(guān)鍵Threadlocal。是用于存放每個線程的looper對象的熄攘,存取的方式是通過get/set。相當(dāng)于一個map的存放方式挪圾。鍵位key是當(dāng)前線程的實(shí)例。value就是looper對象洛史。所以每次創(chuàng)建looper都會去ThreadLocal里面找有沒有當(dāng)前線程的looper。
如何知道 message 發(fā)送到哪個handler處理
當(dāng)使用 Handler.sendMessage() 發(fā)送消息時,調(diào)用 enqueueMessage 方法內(nèi)有 msg.target = this 將 Handler 實(shí)例賦值給 msg 對象土思。當(dāng) loop() 取出消息時,調(diào)用 dispatchMessage 方法根據(jù) target 屬性己儒,回調(diào)對應(yīng) handler 實(shí)例的 handlerMessage 方法處理消息。
具體流程圖:
- App啟動時創(chuàng)建一個主線程(UI)闪湾,接著UI線程會創(chuàng)建一個Looper,同時也會在在Looper內(nèi)部創(chuàng)建一個消息隊列途样。而在創(chuàng)鍵Handler的時候取出當(dāng)前線程的Looper,并通過該Looper對象獲得消息隊列(MessageQueue)何暇,然后Handler在子線程中通過MessageQueue.enqueueMessage在消息隊列中添加一條Message。
- 通過Looper.loop() 開啟消息循環(huán)不斷輪詢調(diào)用 MessageQueue.next()裆站,取得對應(yīng)的Message并且通過Handler.dispatchMessage傳遞給Handler,最終調(diào)用Handler.handlerMessage處理消息宏胯。
源碼分析:
- 插入消息
MessageQueue.enqueueMessage:Handler調(diào)用sendMessage()發(fā)送消息,而Handler內(nèi)部通過Looper對象得到MessageQueue對象后又調(diào)用MessageQueue.enqueueMessage方法肩袍。
boolean enqueueMessage(Message msg, long when) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//插入前先消息隊列是否有消息,新的頭了牛,如果被阻止辰妙,則喚醒事件隊列。
if (p == null || when == 0 || when < p.when) {
//將消息放進(jìn)隊列頭部
msg.next = p;
mMessages = msg;
needWake = mBlocked;//指示next()是否被阻止在pollOnce()中以非零超時等待密浑。如果阻塞,則需要喚醒looper
} 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.
/*插入隊列中間尔破。 通常,除非隊列的開頭有障礙并且消息是隊列中最早的
異步消息懒构,否則我們不必喚醒事件隊列。(隊列中消息不為空胆剧,并且next()也沒有阻塞)*/
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;
}
// 如果looper阻塞/休眠中,則喚醒looper循環(huán)機(jī)制處理消息
if (needWake) {
nativeWake(mPtr);//喚醒
}
}
return true;
}
調(diào)用nativeWake喚醒(這部分內(nèi)容出自頭部連接秩霍,詳細(xì)源碼分析可看前輩的)
//android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr){
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
void NativeMessageQueue::wake(){
mLooper->wake();
}
void Looper::wake(){
...
//往mWakeEventFd 中write 1,用以喚醒 looper
ssize_t mWrite = TEMP_FAILURE_READY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
}
既然有寫入消息铃绒,那必定要把消息處理掉,所以喚醒了epoll_wait()颠悬,然后繼續(xù)方法調(diào)動awoken(),這個方法就是將之前寫入的1讀出,表示消費(fèi)這個事件
void Looper::awaken(){
...
//讀取頭部消息赔癌,靜默處理掉
TEMP_FAILURE_READY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}
隨后在Java 層的next()@MessageQueue 就被喚醒,讀取在enqueueMessage()@MessageQueue 插在隊頭的消息進(jìn)行處理
- Looper循環(huán)讀取消息
當(dāng)looper循環(huán)機(jī)制在MessageQueue的next()讀取消息時發(fā)現(xiàn)消息隊列中沒有消息時届榄,就會調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis);將next()阻塞在PollOnce中。looper也就進(jìn)入了休眠期铝条。
@UnsupportedAppUsage
Message next() {
// 如果消息循環(huán)已經(jīng)退出并被處理,請返回此處班缰。
// 如果應(yīng)用程序嘗試退出后不支持的循環(huán)程序,則會發(fā)生這種情況埠忘。
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;//判斷消息隊列中是否有消息
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//就是在這里根據(jù)nextPollTimeoutMillis判斷是否要阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
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) {//隊列中拿到的消息不為null
if (now < msg.when) {
// 下一條消息尚未準(zhǔn)備好。 設(shè)置超時以使其在準(zhǔn)備就緒時醒來鉴腻。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 正常返回處理
...
} else {
// 隊列中沒有消息,標(biāo)記阻塞looper循環(huán)進(jìn)入休眠
nextPollTimeoutMillis = -1;
}
// 現(xiàn)在已處理所有掛起的消息爽哎,處理退出消息蜓席。
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// 空閑句柄僅在隊列為空或?qū)硪幚黻犃兄械牡谝粭l消息(可能是屏障)時才運(yùn)行课锌。
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
...
}
...
// 將空閑處理程序計數(shù)重置為0,這樣我們就不會再次運(yùn)行它們渺贤。
pendingIdleHandlerCount = 0;
// 在調(diào)用空閑處理程序時,可能已經(jīng)傳遞了一條新消息癣亚,
//因此返回并再次查找未處理消息获印,而無需等待述雾。
nextPollTimeoutMillis = 0;
}
}