<h3><b>基本概述</b></h3>
在Android開發(fā)中提到消息機(jī)制應(yīng)該所有人都不陌生,但是估計(jì)也很少有人能把消息機(jī)制詳細(xì)地說出個(gè)所以然來,我們?cè)陂_發(fā)過程中,有很多技術(shù)痛點(diǎn),就是明明很多時(shí)候我們?cè)谟玫臇|西,我們卻不知道是為什么,它具體是個(gè)什么東西,很多時(shí)候我們只在乎它如何被拿來用,而不去關(guān)心更深層次的東西,也許這就是初中級(jí)向上進(jìn)階的阻礙吧。還好,我意識(shí)到了這一點(diǎn),盡力去了解更多事實(shí)的真相...一不小心說了這么多廢話,言歸正傳吧。
在Android中,我們提到的消息機(jī)制常常指的就是Handler機(jī)制,Handler是Android中的上層接口,在開發(fā)的過程中,我們可以利用Handler將一件事情切換到Handler所在的線程中去執(zhí)行(好好理解這句話)。為什么會(huì)有這樣的機(jī)制出現(xiàn)?我們來考慮一下,在實(shí)際的操作中,我們發(fā)現(xiàn),Handler機(jī)制的運(yùn)用主要是在UI更新上,當(dāng)我們需要做一個(gè)耗時(shí)操作時(shí),我們不可能在mainThread中做這個(gè)事情,因?yàn)锳NR的存在,這時(shí)我們需要開啟一個(gè)新的線程去做這個(gè)事情,當(dāng)事件完成后再通過Handler去告訴mainThread現(xiàn)在可以更新UI了。這就是對(duì)消息機(jī)制的一個(gè)很寬泛的描述。這時(shí)候,我們應(yīng)該考慮一個(gè)問題?為什么我們不干脆在開啟的新線程中更新UI呢,應(yīng)該很多人會(huì)說是因?yàn)锳ndroid不允許這樣做?那么, 為什么不允許呢?
如果不限制UI的更新規(guī)則(即可在任何線程中操作UI)會(huì)帶來什么樣的壞處?如果所有線程都可以改變UI,那我們?nèi)绾伪WCUI朝著我們預(yù)期的方向發(fā)展?這就像是共享內(nèi)存一樣,同時(shí)的操作會(huì)讓UI的狀態(tài)變得不可預(yù)期。那么,如果加上鎖機(jī)制呢?這樣必定會(huì)大大降低UI的訪問效率,因?yàn)殒i機(jī)制會(huì)阻塞線程,就會(huì)讓我們的應(yīng)用在很多情況下看起來像失了智一樣,卡頓嚴(yán)重,所以Handler機(jī)制油然而生了。
<h3><b>工作原理</b></h3>
Android的Handler機(jī)制它的工作原理是怎樣的呢?我們?cè)诰€程中創(chuàng)建一個(gè)Handler對(duì)象,當(dāng)使用它的時(shí)候會(huì)發(fā)現(xiàn)系統(tǒng)會(huì)報(bào)這樣的錯(cuò):
can not create handler inside thread that has not called Looper.prepare();
它描述了在一個(gè)線程中我們不能在沒有Looper的前提下去使用Handler,也就是說Handler機(jī)制的實(shí)現(xiàn)需要Looper這個(gè)東西,那么Looper是啥呢?我們可以通過Looper.prepare()在當(dāng)前線程中創(chuàng)建一個(gè)Looper對(duì)象,當(dāng)我們查閱源碼的時(shí)候可以發(fā)現(xiàn),Looper.prepare()做了什么,實(shí)際上它創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)。Looper相關(guān)源碼如下:
public static void prepare(){
prepare(true);
}
public static void prepare(boolean quitAllowed){
if( sThreadLocal.get()!=null ){
...
}
sThreadLocal.set(new Looper(quitAllowed));
}
public Looper(){
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在創(chuàng)建了Looper之后,我們不僅僅得到了looper對(duì)象,還得到了一個(gè)消息隊(duì)列messageQueue,這時(shí)候才構(gòu)建了一個(gè)完整的Handler機(jī)制。它們是如何分工的。
線程內(nèi)部負(fù)責(zé)處理業(yè)務(wù)邏輯,looper的作用負(fù)責(zé)消息的存取查閱,其中messageQueue是消息的存儲(chǔ)結(jié)構(gòu),它是單鏈表形式,looper負(fù)責(zé)消息的輪詢,handler負(fù)責(zé)發(fā)送和處理消息。looper、線程、messageQueue是一一對(duì)應(yīng)的,在不同的線程中它們各自有著對(duì)應(yīng)的模塊。我們還能看到創(chuàng)建的looper對(duì)象被放入了ThreadLocal中去,這樣的作用是,各個(gè)線程中的Looper對(duì)象是相對(duì)獨(dú)立,誰也影響不到誰,如何達(dá)到這個(gè)效果的呢?我們就來看一下ThreadLocal這個(gè)類。
在實(shí)際使用的過程中我們發(fā)現(xiàn),運(yùn)行在主線程的Handler我們不需要事先為它創(chuàng)建一個(gè)Looper來進(jìn)行消息管理,這是為什么?這是因?yàn)橄到y(tǒng)早已經(jīng)默認(rèn)給主線程創(chuàng)建了Looper對(duì)象。
<h3><b>ThreadLocal類</b></h3>
從它的名字來看,它似乎是個(gè)什么線程,其實(shí)不然ThreadLocal不是線程但和線程有關(guān),具體的來講,它是一個(gè)和線程相關(guān)的存儲(chǔ)方式。它是線程內(nèi)部的存儲(chǔ)方式,只有在當(dāng)前線程內(nèi)可以獲得這個(gè)值。它的內(nèi)部是如何實(shí)現(xiàn)的?我們通過查看源碼不難發(fā)現(xiàn),它是一個(gè)類似于數(shù)組且以key-value方式存儲(chǔ)的一種數(shù)據(jù)格式。
ThreadLocal內(nèi)部存儲(chǔ)是數(shù)組結(jié)構(gòu),它以當(dāng)前線程作為Key來區(qū)別各個(gè)線程之間的數(shù)據(jù),查看源碼可以發(fā)現(xiàn),在偶數(shù)位它用來存放當(dāng)前線程,在此的后一位來存放具體的數(shù)據(jù)。所以通過不同的線程可以定位到不同位置的key,然后向后延一位就能得到存儲(chǔ)的數(shù)據(jù),這就是ThreadLocal能做到各個(gè)線程間互不干涉的所在。它最典型的應(yīng)用就是Handler機(jī)制中Looper的構(gòu)建。
我們下面來舉個(gè)很簡單的例子,我們來看偽代碼:
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>;
// 主線程
mThreadLocal.set(true);
Log.i("threadLocal", mThreadLocal.get());
// 線程1
new Thread("Thread1"){
@ovrride
public void run(){
mThreadLocal.set(false);
Log.i("threadLocal", mThreadLocal.get());
}
}
// 線程2
new Thread("Thread2"){
@ovrride
public void run(){
Log("threadLocal", mThreadLocal.get());
}
}
上面的例子中,我們分別在主線程,線程1,線程2中對(duì)mThreadLocal變量進(jìn)行了操作,并且在這三個(gè)地方分別打印該變量的值,如果是普通的變量,那么我想這些打印的值恐怕只能靠猜了...,而現(xiàn)在,我們了解了ThreadLocal的存儲(chǔ)原理,不難分析出各個(gè)位置打印的值。
因?yàn)樗鼈儾粫?huì)相互影響, 所以在主線程中設(shè)置了true,那么我以主線程去保存數(shù)據(jù),我在打印的時(shí)候還以主線程為key去獲取對(duì)應(yīng)的值,這個(gè)值一定是當(dāng)時(shí)存儲(chǔ)的值true。同樣的判斷,在線程1中打印的值是false。而在線程2中,我們從來沒有在對(duì)應(yīng)的存儲(chǔ)空間上為線程2來開辟存儲(chǔ)空間,所以它的值應(yīng)該是null。
<h3><b>消息隊(duì)列</b></h3>
我們?cè)谏厦娴慕榻B中可以知道,Looper對(duì)象是在其內(nèi)部創(chuàng)建了一個(gè)消息隊(duì)列用來存儲(chǔ)消息的,所以在了解Looper之前,我們有必要知道消息隊(duì)列的一些事情。
消息隊(duì)列在Android中指的就是MessageQueue,從它的名字上來看是個(gè)隊(duì)列,實(shí)際上它的內(nèi)部實(shí)現(xiàn)不是隊(duì)列,而是單鏈表結(jié)構(gòu)的,它提供了消息機(jī)制中消息的插入和讀取。
說點(diǎn)其他的,我們應(yīng)該知道鏈表的概念,鏈表的概念是從C語言中引用而來的,它表示每個(gè)消息是相連的,每一個(gè)消息在鏈表中稱作一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)不僅僅保存數(shù)據(jù),它還保存了一個(gè)指針用來指向下一個(gè)節(jié)點(diǎn)的地址,所以它的優(yōu)點(diǎn)在可以高效率地插入、刪除。因?yàn)橄㈥?duì)列常用的操作就是插入消息和取出消息,而取出消息實(shí)質(zhì)上就是將該節(jié)點(diǎn)從數(shù)據(jù)結(jié)構(gòu)中剔除,所以消息隊(duì)列會(huì)選取鏈表結(jié)構(gòu)來存儲(chǔ)消息。
<h3><b>Looper</b></h3>
我們從上面的分析中可以知道,Handler的工作需要Looper,也知道可以通過Looper.prepare()為Handler創(chuàng)建一個(gè)Looper。我們通過Looper.loop()開啟Looper輪詢,此時(shí)Looper會(huì)不停的輪詢消息隊(duì)列中的數(shù)據(jù),一有消息通過Handler發(fā)出時(shí),Looper就能捕獲這個(gè)消息,并通過這個(gè)函數(shù)來處理:
msg.target.dispatchMessage(msg)
其實(shí),msg.target實(shí)際上就是發(fā)出消息的那個(gè)Handler,調(diào)用Handler的dispatchMessage(msg)函數(shù)來最終處理這個(gè)消息。
每個(gè)線程都有自己的Looper,非主線程我們是不可預(yù)期的,但是主線程永遠(yuǎn)只有一個(gè),所以我們可以通過靜態(tài)方法Looper.getMainLooper()隨時(shí)獲取主線程的Looper。
我們說過,如果消息隊(duì)列中不存在消息時(shí),Looper將會(huì)處于阻塞狀態(tài),這無疑增加了系統(tǒng)的開銷,所以當(dāng)我們確定不再有消息傳遞進(jìn)來時(shí),可以通過Looper.quit()或Looper.quitSately()方法來退出Looper。前者是一調(diào)用立馬退出Looper,后者是需要將現(xiàn)有的消息處理完之后再退出Looper,相對(duì)來講后者更有責(zé)任心一些,但這也不一定就是必要的。
<h3><b>Handler</b></h3>
說了這么多是時(shí)候來認(rèn)識(shí)最后的東西了。從以上種種我們的介紹,我們可以很清晰地知道,在Handler消息機(jī)制中,Handler做扮演的角色的作用就是發(fā)送和接收消息。
我們經(jīng)常性的操作來用語言描述一次。我們?cè)谥骶€程中定了一個(gè)Handler,由于系統(tǒng)已經(jīng)默認(rèn)幫我們把Looper創(chuàng)建好了,所以我們不需要再去創(chuàng)建Looper。這時(shí)候我們需要向服務(wù)器進(jìn)行一個(gè)耗時(shí)的請(qǐng)求,那么我們開啟了線程a去請(qǐng)求數(shù)據(jù),當(dāng)數(shù)據(jù)請(qǐng)求完成后,我們希望數(shù)據(jù)可以在UI上(即主線程)上展示,所以我們利用Handler將線程a取到的數(shù)據(jù)發(fā)送出去。Looper循環(huán)機(jī)制接收并處理了該消息,最終該消息交由Handler所在的線程(主線程)處理。我們通過Handler機(jī)制將UI操作從線程a中搬到了主線程中去。
那么Handler是如何處理消息的?我們看到 msg.target.dispatchMessage(msg) 這個(gè)函數(shù),略過長篇大論的判斷和callback,在日常開發(fā)中,創(chuàng)建Handler最常用的就是派生一個(gè)Handler的子類,并重寫handlerMessage()方法來處理具體的消息。
<h3><b>進(jìn)一步認(rèn)清Handler</b></h3>
當(dāng)我們了解以上Android的消息機(jī)制后,我們不禁沾沾自喜,偶爾看到了這樣的代碼:
new Handler().postDelayed(new Runnable(){
@Override
public void run(){
Toast.makeText(mContext, "hellow", Toast.LENGHT_LONG).show();
}
}, 1000);
乍一看,咦?在非主線程中更新UI?竟然不報(bào)錯(cuò)...,好吧我們來分析一下吧。首先我們來看Handler的構(gòu)造函數(shù)。
1. Handler的構(gòu)造函數(shù)
public Handler(){
this(null, false);
}
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
此時(shí)Handler做了一些準(zhǔn)備工作,比如準(zhǔn)備Looper等,此時(shí)一個(gè)變量mCallback==null。此時(shí)我們將Looper中的隊(duì)列和Handler中的隊(duì)列關(guān)聯(lián)起來。
2. 我們?cè)賮砜磒ostDelayed()函數(shù)
public final boolean postDelayed(Runnable r, long delayMillis){
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
很顯然我們將Runnable這個(gè)線程設(shè)置為了Message的callback屬性。我們經(jīng)過跟蹤,依次調(diào)用了這些方法。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
從 msg.target = this; 這句話看出msg.target就是Handler,也就是說此時(shí)MessageQueue和Handler發(fā)生了聯(lián)系,這個(gè)enqueueMessage方法很顯然就是我們?cè)贛essageQueue中講的入列方法,即將消息加入到了對(duì)應(yīng)線程的Looper消息隊(duì)列中去了,此時(shí)Looper獲取到了這個(gè)消息,并且我們上面講Looper時(shí)提到了Looper調(diào)用這個(gè)函數(shù)來處理消息msg.target.dispatchMessage(msg),msg.target是什么?就是Handler啊,所以這個(gè)消息由Handler的dispatchMessage(msg)函數(shù)來處理消息。
3. dispatchMessage()方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
通過步驟2可以知道,msg.callback!=null,而是runnable。所以執(zhí)行handlerCallback(msg);
4. handlerCallback(msg)方法
private static void handleCallback(Message message) {
message.callback.run();
}
這樣,就會(huì)執(zhí)行我們?cè)贏ctivity 的Runnable 中的run 方法了,也就是顯示Toast。所以為什么這個(gè)Toast是沒問題的呢?
這是因?yàn)?,我們將Runnable設(shè)置給了Message的屬性callback,這個(gè)callback連同Message被傳遞到Handler所在的線程(即UI線程)中去了,所以它實(shí)際上試運(yùn)行在UI線程上的。
至此,我們應(yīng)該對(duì)Handler機(jī)制有了比較深刻的理解了。
如果有你認(rèn)為不恰當(dāng)?shù)拿枋觯环亮粞愿嬖V我...