Android 的消息機(jī)制應(yīng)該是入門 Android 就會(huì)接觸到的,很多人第一個(gè)接觸的可能就是 Handler 的使用,但其實(shí)那只是冰山一角,今天就來(lái)回顧梳理一下這方面的知識(shí)點(diǎn),雖然很基礎(chǔ),但依然值得再去看,按哲學(xué)的思想,每次來(lái)看都是新的。
Looper
提供了 prepare 方法,當(dāng)然考慮到主線程的特殊性,單獨(dú)提供了 prepareMainLooper 方法。prepare 的工作主要就是創(chuàng)建 Looper 對(duì)象,并將該對(duì)象和當(dāng)前線程相關(guān)聯(lián)。
用來(lái)做關(guān)聯(lián)的就是 ThreadLocal 數(shù)據(jù)結(jié)構(gòu),更準(zhǔn)確的說(shuō) ThreadLocal 內(nèi)部有個(gè)ThreadLocalMap 靜態(tài)類,它是線程對(duì)象為 key,與之關(guān)聯(lián)的對(duì)象為 value 的一個(gè)集合。
MessageQueue
在創(chuàng)建 Looper 對(duì)象時(shí)創(chuàng)建,就是我們常說(shuō)的消息隊(duì)列。消息隊(duì)列的操作將通過(guò) Looper 對(duì)象實(shí)現(xiàn),例如 enqueueMessage,next 等。MessageQueue 里消息隊(duì)列的實(shí)現(xiàn)用到了鏈表結(jié)構(gòu)。
Message
作為消息的載體,它是一個(gè)鏈表節(jié)點(diǎn)形式的數(shù)據(jù)結(jié)構(gòu)。里面包含了一個(gè)常用字段,what, 表示消息類型,好在處理消息時(shí)能知道怎么處理,arg1, arg2, 用來(lái)簡(jiǎn)單的傳遞一些 int 值,obj, 按照源碼注釋,其實(shí)是用于進(jìn)程間傳遞的數(shù)據(jù),但用于平常的數(shù)據(jù)傳遞也可以,data, 這個(gè)是 Bundle 類型,除前面說(shuō)的場(chǎng)景以外,其他情況的數(shù)據(jù)傳遞就可以用 setData 方法來(lái)賦值,callback 是個(gè) Runnable 類型的回調(diào),后面在 Handler 里會(huì)用到。
說(shuō) Message 是個(gè)鏈表節(jié)點(diǎn),那就必須會(huì)有指向下一個(gè)的 next 變量。另外,可能考慮到 Message 對(duì)象會(huì)頻繁的創(chuàng)建和釋放,在實(shí)現(xiàn)上,增加了一個(gè)消息池的緩存機(jī)制。
這個(gè)消息池其實(shí)也是一個(gè)鏈表。當(dāng)需要?jiǎng)?chuàng)建新消息時(shí),先從消息池的鏈表頭拿一個(gè),如果消息池里沒(méi)有,就會(huì)新創(chuàng)建。當(dāng)消息用完后,會(huì)被回收進(jìn)池子(從消息池頭部插入的方式),當(dāng)然如果池子無(wú)限制的緩存下去也是不現(xiàn)實(shí)的,所以池子最大是 50.
有了以上三個(gè)角色仿佛消息機(jī)制可以運(yùn)轉(zhuǎn)了,但可能考慮到面向?qū)ο蟮脑O(shè)計(jì)原則吧,對(duì)于消息的發(fā)送和處理工作交給了 Handler,而 Looper 則負(fù)責(zé)運(yùn)營(yíng)好消息隊(duì)列。這樣一來(lái),各自職責(zé)清晰單一。
Handler
Handler 對(duì)象就是那個(gè)打工人,它歸順于它的消息隊(duì)列(或者說(shuō) Looper,線程),一但來(lái)消息了就處理,一但需要發(fā)消息了就發(fā)送,可如果不是它所屬的消息隊(duì)列里有消息,它是不會(huì)管的。
Handler 對(duì)象的創(chuàng)建方式主要分兩類,一類是指定 Looper,這樣一來(lái)也就指定了線程,和消息隊(duì)列,另一類則是默認(rèn)的以當(dāng)前所在線程關(guān)聯(lián)的 Looper 為默認(rèn)值。
在消息處理上,我們需要留意一下處理優(yōu)先級(jí),
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
}
}
首先,如果 Message 對(duì)象的 callback 變量不為空,則會(huì)對(duì)該回調(diào)進(jìn)行處理消費(fèi),且不再處理其他字段的數(shù)據(jù)。這種情況對(duì)應(yīng)怎么樣發(fā)消息呢?就是通過(guò) Handler 的 post 方法,消息會(huì)以 Runnable 的形式添加到隊(duì)列中,常用的還有 postDelayed。
其次,Handler 的構(gòu)造方法可以傳入 Callback 接口對(duì)象,之后能通過(guò)它去回調(diào) Message 對(duì)象并處理。這種情況是針對(duì),不想創(chuàng)建 Handler 子類,僅想去處理消息的場(chǎng)景,其實(shí)我們平常應(yīng)該用這種情況比較多,但實(shí)際上我們通常會(huì)去實(shí)現(xiàn) Handler 子類,并重寫(xiě) handleMessage 方法,來(lái)處理消息,這也就是最后的消息處理。
所以對(duì)于一個(gè)消息的處理,首先判斷消息本身的 callback 存不存在,不存在則會(huì)通過(guò) Handler 對(duì)象的 mCallback 處理,如果 mCallback 對(duì)象也不存在,再去看 handleMessage 方法能不能處理。
一個(gè)消息隊(duì)列可以有多個(gè) Handler 對(duì)象嗎?
構(gòu)造方法里沒(méi)有看到數(shù)量的限制,所以對(duì)于同一個(gè)線程,可以創(chuàng)建任意多個(gè) Handler 對(duì)象。
A Handler 對(duì)象發(fā)出的消息可以由 B Handler 對(duì)象處理嗎?
這點(diǎn)應(yīng)該是不行的,A Handler 發(fā)送的消息只能 A Handler 處理。舉個(gè)反例,A Handler 能處理 what == 1 的消息,B Handler 也能處理,此時(shí)如果 A 發(fā)送了一個(gè)該類型消息,B 去處理了,那么結(jié)果可能就不是 A 想要的,或者 A 都感知不到。
再結(jié)合源碼看下,
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//Handler 發(fā)送消息最終會(huì)調(diào)用該方法
//Message 對(duì)象的 target 變量就是一個(gè) Handler 類型
//此時(shí) Message 將當(dāng)前發(fā)送的 Handler 賦值給了 target,兩者有了某種關(guān)系
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
添加到消息隊(duì)列后,Looper 對(duì)象會(huì)在檢查消息隊(duì)列時(shí)發(fā)現(xiàn)這個(gè)消息并取出準(zhǔn)備進(jìn)行處理。這個(gè)檢查是個(gè)無(wú)限循環(huán),也就是說(shuō)在該線程的生命周期上都會(huì)一直檢查。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
try {
//從這句可以看到,Message 對(duì)象調(diào)用的就是 target 變量的 dispatchMessage 方法
//這也就驗(yàn)證了 A Handler 發(fā)出的消息只能 A Handler 處理。
msg.target.dispatchMessage(msg);
}
//將 Message 對(duì)象回收到消息池里
msg.recycleUnchecked();
}
}
由此分析,再延伸出兩個(gè)問(wèn)題,
這里的無(wú)限循環(huán)不會(huì)造成線程卡死嗎?
這原理上分析起來(lái)還挺麻煩,簡(jiǎn)單來(lái)說(shuō)就是采用了 Linux 的 pipe/epoll 機(jī)制,在執(zhí)行 queue.next 這步時(shí),如果隊(duì)列里沒(méi)有消息就會(huì)調(diào)用 nativePollOnce 使線程進(jìn)入休眠狀態(tài),這樣一來(lái)線程就會(huì)釋放資源,代碼執(zhí)行也就阻塞在這里了。當(dāng)有新的消息進(jìn)入隊(duì)列后,會(huì)調(diào)用 nativeWake 喚起線程,這樣也就能繼續(xù)執(zhí)行下去了。
如何結(jié)束這個(gè)無(wú)限循環(huán)?
調(diào)用 quit 方法,但對(duì)于主線程,默認(rèn)是不允許退出的,如果強(qiáng)行退出將會(huì)拋異常結(jié)束。因?yàn)?Android 設(shè)計(jì)本身就是基于這套消息機(jī)制,像 Activity,Service 等都基于此,主線程循環(huán)的退出也就意味著應(yīng)用生命周期的結(jié)束。
還有一點(diǎn)要了解
當(dāng)我們發(fā)出消息后,不想要這個(gè)消息了,那么在消息被處理之前可以通過(guò) removeMessages 方法來(lái)移除指定 what 的消息,如果想要移除該 Handler 相關(guān)聯(lián)的所有消息,則可以調(diào)用 removeCallbacksAndMessages 方法,入?yún)⒅付?null 即可。
參考內(nèi)容
Android中為什么主線程不會(huì)因?yàn)長(zhǎng)ooper.loop()里的死循環(huán)卡死