對(duì)于Android的異步消息處理機(jī)制,大家一定都不陌生,異步消息處理機(jī)制一個(gè)常見的應(yīng)用場(chǎng)景就是在子線程中更新UI,我們都知道栏赴,Android的UI是線程不安全的,如果在子線程中直接更新UI糟红,便會(huì)導(dǎo)致程序崩潰艾帐。對(duì)于該問題,常見的解決方法是盆偿,在UI線程新建一個(gè)Handler并覆寫其handleMessage方法柒爸,在子線程中獲取Message對(duì)象,通過Message對(duì)象的arg,obj字段以及setData()方法攜帶一些數(shù)據(jù)事扭,之后利用UI線程的Handler將消息發(fā)送出去捎稚,最后便可以在handleMessage方法中獲取到剛剛發(fā)送的消息并進(jìn)行相應(yīng)的處理了,示例代碼如下:
public class MainActivity extends ActionBarActivity {
private Handler handler1=new Handler(){
@Override
public void handleMessage(Message msg){
//to do sth
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable(){
@Override
public void run() {
Message msg=Message.obtain();
msg.what=1;
Bundle bundle = new Bundle();
bundle.putString("info", "info");
msg.setData(bundle);
handler1.sendMessage(msg);
}
}).start();
}
}
這種方法相信大家都已經(jīng)用得很熟了求橄,注意今野,此時(shí)我們是在UI線程創(chuàng)建Handler的,那么現(xiàn)在我們嘗試在子線程創(chuàng)建Handler罐农,看看與之前的UI線程創(chuàng)建Handler有何區(qū)別条霜,示例代碼如下:
public class MainActivity extends ActionBarActivity {
private Handler handler2=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable(){
@Override
public void run() {
handler2=new Handler();
}
}).start();
}
}
結(jié)果,系統(tǒng)竟然報(bào)錯(cuò)了涵亏,錯(cuò)誤提示信息為Can't create handler inside thread that has not called Looper.prepare() 宰睡。意思是蒲凶,不能在一個(gè)沒有調(diào)用Looper.prepare()的線程內(nèi)創(chuàng)建Handler。那么我們依據(jù)系統(tǒng)的意思拆内,在創(chuàng)建Handler之前調(diào)用Looper.prepare()試試看:
new Thread(new Runnable(){
@Override
public void run() {
Looper.prepare();
handler2=new Handler();
}
}).start();
果不其然旋圆,這回終于不報(bào)錯(cuò)了。但是麸恍,僅僅解決問題是不夠的灵巧,我們更應(yīng)該去探究問題背后所隱藏的原理,只有這樣我們的能力才會(huì)有一個(gè)質(zhì)的提升抹沪,所以下面刻肄,我將帶領(lǐng)大家從源代碼級(jí)別深入地探究Android的異步消息處理機(jī)制。
OK,話不多說采够,讓我們趕快進(jìn)入這美妙的探索之旅吧~~~
前面已經(jīng)講到肄方,在子線程中創(chuàng)建Handler時(shí),如果事先不調(diào)用一下Looper.prepare()蹬癌,系統(tǒng)便會(huì)報(bào)錯(cuò)。那么我們?yōu)槭裁匆欢ㄒ热フ{(diào)一下Looper.prepare()呢虹茶?看來逝薪,只有Handler的構(gòu)造函數(shù)以及Looper.prepare()的源代碼能夠告訴我們答案了,我們先從Handler的構(gòu)造函數(shù)看起蝴罪,Handler的構(gòu)造函數(shù)源代碼如下:
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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 = null;
}
我們從mLooper = Looper.myLooper()這行代碼看起董济,如果拿到的mLooper對(duì)象為空的話,便會(huì)拋出一個(gè)運(yùn)行時(shí)異常要门,提示信息正是剛剛的“Can't create handler inside thread that has not called Looper.prepare()”虏肾,那么mLooper對(duì)象何時(shí)為空呢,這就要去看Looper.myLooper()中的代碼了欢搜,Looper.myLooper()的代碼如下:
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
顯然封豪,這個(gè)方法是從sThreadLocal對(duì)象中拿出當(dāng)前的Looper對(duì)象,如果拿不到的話則返回null〕次粒現(xiàn)在我們可以大膽猜測(cè)下在什么地方給sThreadLocal對(duì)象設(shè)置Looper的了吹埠,沒錯(cuò),就是在Looper.prepare()方法中疮装,我們趕緊去看一下Looper.prepare()方法的源碼:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
在Looper.prepare()中缘琅,會(huì)先去嘗試獲取sThreadLocal中的Looper對(duì)象,如果當(dāng)前能夠獲取到Looper對(duì)象廓推,則拋出運(yùn)行時(shí)異乘⑴郏“Only one Looper may be created per thread”,這也說明了每個(gè)線程最多只能有一個(gè)Looper對(duì)象樊展。如果獲取不到Looper對(duì)象呻纹,則給sThreadLocal設(shè)置Looper對(duì)象堆生。
說到這里,很多朋友可能都會(huì)有疑惑:為什么主線程中沒有調(diào)用Looper.prepare()居暖,卻依然能夠正常地創(chuàng)建Handler呢顽频?
這是因?yàn)槌绦蛟趩?dòng)時(shí),系統(tǒng)已經(jīng)自動(dòng)幫我們調(diào)用了Looper.prepare()了太闺,具體可以參照ActivityThread中的main()方法糯景,這里就不去詳述了,感興趣的朋友可以去自行查閱省骂。
我們繼續(xù)去分析Handler中的源代碼:
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 = null;
可以看到蟀淮,在利用Looper.myLooper()獲取到Looper對(duì)象并賦值給Handler的成員變量mLooper之后,又將 mLooper的mQueue賦值給Handler的成員變量mQueue钞澳,由此可見怠惶,Handler中擁有著Looper和MessageQueue兩個(gè)成員變量,Handler的構(gòu)造函數(shù)的主要目的就是初始化這兩個(gè)成員變量轧粟,同時(shí)策治,一個(gè)Looper對(duì)應(yīng)著一個(gè)MessageQueue。
分析完Handler的構(gòu)造函數(shù)以及Looper.prepare()的源代碼兰吟,我們?cè)賮硌芯恳幌孪⒌陌l(fā)送流程通惫,先溫習(xí)一下消息發(fā)送的代碼:
Message msg=Message.obtain();
msg.what=1;
Bundle bundle = new Bundle();
bundle.putString("info", "info");
msg.setData(bundle);
handler1.sendMessage(msg);
看到這里,我們不禁要問:Handler到底將消息發(fā)到哪里混蔼?為什么之后在handleMessage中又可以收到之前發(fā)送的消息履腋?
我們知道,Handler有很多發(fā)送Message的方法惭嚣,其中遵湖,除了sendMessageAtFrontOfQueue方法,其他方法都會(huì)輾轉(zhuǎn)調(diào)用到sendMessageAtTime方法晚吞,sendMessageAtTime方法的源代碼如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
可以看到延旧,sendMessageAtTime方法有兩個(gè)參數(shù),第一個(gè)參數(shù)為msg,代表著我們發(fā)送的消息载矿,第二個(gè)參數(shù)為uptimeMillis垄潮,代表著發(fā)送消息的時(shí)間,其值為自系統(tǒng)開機(jī)到當(dāng)前時(shí)間的毫秒數(shù)加上延遲時(shí)間闷盔,如果不是調(diào)用的sendMessageDelayed方法弯洗,則延遲時(shí)間為0.之后,將當(dāng)前Handler的MessageQueue對(duì)象(即mQueue)取出逢勾,判斷MessageQueue對(duì)象是否為空牡整,若不為空,則將當(dāng)前消息的target屬性指向當(dāng)前發(fā)送消息的Handler對(duì)象(即this),最后溺拱,調(diào)用我們MessageQueue對(duì)象的enqueueMessage方法讓消息進(jìn)入消息隊(duì)列中逃贝。
這里要稍微解釋下MessageQueue,顧名思義,MessageQueue就是一個(gè)消息隊(duì)列谣辞,其內(nèi)部會(huì)以一個(gè)隊(duì)列的形式存儲(chǔ)我們發(fā)送的消息,并提供了消息的入隊(duì)和出隊(duì)方法沐扳。
接下來就要分析較為關(guān)鍵的enqueueMessage方法了泥从,enqueueMessage方法會(huì)將消息放入消息隊(duì)列中,并按照消息發(fā)送的時(shí)間對(duì)消息進(jìn)行排序沪摄,enqueueMessage方法的源代碼如下:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
enqueueMessage方法有兩個(gè)參數(shù)躯嫉,第一個(gè)是入隊(duì)的消息,第二個(gè)是該消息發(fā)送的時(shí)間杨拐。前面提到的sendMessageAtFrontOfQueue方法也會(huì)調(diào)用到enqueueMessage方法祈餐,但傳入的消息發(fā)送時(shí)間為0。在enqueueMessage方法的內(nèi)部哄陶,會(huì)將當(dāng)前入隊(duì)消息的when字段設(shè)置為傳入的消息發(fā)送時(shí)間帆阳,取出當(dāng)前的隊(duì)頭消息并賦給變量p,之后判斷如果當(dāng)前隊(duì)頭消息為空或消息發(fā)送時(shí)間為0或消息發(fā)送時(shí)間小于隊(duì)頭消息的時(shí)間,則將當(dāng)前入隊(duì)消息的next指針指向隊(duì)頭消息屋吨,之后將隊(duì)頭消息重新賦值為入隊(duì)消息蜒谤,從而完成了將入隊(duì)消息插入到消息隊(duì)列頭部的操作。如果不滿足上述的三種情況至扰,則按照消息發(fā)送時(shí)間的先后順序?qū)ふ胰腙?duì)消息在隊(duì)列中的插入位置芭逝,之后將入隊(duì)消息插入即可。
上面是對(duì)消息的入隊(duì)操作的分析渊胸,那么出隊(duì)操作在哪里呢?我們就要去分析一下Looper.loop()的源碼了:
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
msg.recycle();
}
}
}
在Looper.loop()中台妆,會(huì)進(jìn)入一個(gè)死循環(huán)翎猛,不斷調(diào)用當(dāng)前MessageQueue的next()方法取出下一條待處理的消息,如果當(dāng)前沒有待處理的消息則阻塞接剩。之后切厘,當(dāng)消息不為空且消息的target字段不為空的話,調(diào)用消息的target字段的dispatchMessage方法懊缺,注意疫稿,此時(shí)消息的target字段就是當(dāng)初發(fā)送這條消息的Handler對(duì)象。
接下來鹃两,我們進(jìn)入到Handler的dispatchMessage方法的源代碼中:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在Handler的dispatchMessage方法中遗座,首先會(huì)去判斷消息的callback字段是否為空,若不為空俊扳,則調(diào)用handleCallback方法對(duì)消息進(jìn)行處理途蒋,若為空,則再去判斷Handler的mCallback字段是否為空(Handler的無參構(gòu)造函數(shù)中mCallback被設(shè)置為空)馋记,若不為空号坡,則調(diào)用mCallback的handleMessage方法懊烤,若mCallback字段為空,則直接調(diào)用 handleMessage方法宽堆。
至此腌紧,我們已經(jīng)從源代碼級(jí)別回答了上面提出的兩個(gè)問題:Handler到底將消息發(fā)到哪里?為什么之后在handleMessage中又可以收到之前發(fā)送的消息畜隶?相信大家一定都有很深的理解了吧_壁肋。
下面介紹一個(gè)Android異步消息處理線程的最標(biāo)準(zhǔn)的寫法,此寫法引自Android官方文檔:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
現(xiàn)在代箭,我們來思考一個(gè)非常關(guān)鍵的問題:handleMessage方法何時(shí)運(yùn)行在主線程中墩划,何時(shí)運(yùn)行在子線程中?
有不少朋友可能會(huì)說嗡综,handleMessage方法的定義位于主線程中乙帮,其就會(huì)在主線程中執(zhí)行,handleMessage方法的定義位于子線程中极景,其就會(huì)在子線程中執(zhí)行察净。
事實(shí)真的是這樣嗎?
其實(shí)不然盼樟,我先舉個(gè)反例氢卡。HandlerThread就是一個(gè)典型的反例。我們?cè)谥骶€程中定義Handler并覆寫其handleMessage方法晨缴,在定義Handler時(shí)译秦,我們傳入一個(gè)已經(jīng)啟動(dòng)的HandlerThread對(duì)象的Looper作為參數(shù),那么击碗,我們此時(shí)的handleMessage方法是運(yùn)行在子線程中的筑悴,但此時(shí)我們handleMessage方法的定義是位于主線程中的。
這是怎么回事呢稍途?明明handleMessage方法的定義是位于主線程中的阁吝,怎么會(huì)運(yùn)行在子線程里面呢?看來還得再次分析一下我們的源碼械拍,不過這一次突勇,我們需要對(duì)源碼進(jìn)行逆向分析。
首先坷虑,我們知道甲馋,handleMessage方法是在dispatchMessage方法中被調(diào)用的,而dispatchMessage方法又是在Looper.loop()中調(diào)用的猖吴,也就是說摔刁,如果Looper.loop()運(yùn)行在主線程,handleMessage方法也會(huì)運(yùn)行在主線程,如果Looper.loop()運(yùn)行在子線程共屈,handleMessage方法也會(huì)運(yùn)行在子線程绑谣。那么我們的Looper.loop()到底是運(yùn)行在主線程,還是在子線程呢拗引?其實(shí)借宵,這就要看我們定義的Handler用的是哪個(gè)線程的Looper了,如果我們定義的Handler用的是主線程的Looper矾削,那么它使用的MessageQueue自然也是主線程Looper對(duì)應(yīng)的MessageQueue壤玫,通過該Handler發(fā)送的消息會(huì)進(jìn)入該MessageQueue中,之后會(huì)在主線程的Looper對(duì)應(yīng)的Looper.loop()方法中不斷取出該MessageQueue中的消息進(jìn)行處理哼凯,注意欲间,此時(shí)我們主線程的Looper對(duì)應(yīng)的Looper.loop()方法也是運(yùn)行在主線程中的,所以此時(shí)我們的handleMessage方法也是運(yùn)行在主線程中的断部。定義的Handler用的是子線程的Looper的分析過程同上猎贴。總結(jié)一下,如果創(chuàng)建Handler時(shí)用的是主線程的Looper,則相應(yīng)的handleMessage方法會(huì)運(yùn)行在主線程中蝴光,如果創(chuàng)建Handler時(shí)用的是子線程的Looper,則相應(yīng)的handleMessage方法會(huì)運(yùn)行在子線程中她渴。
看到這里,相信大家已經(jīng)對(duì)Android的異步消息處理機(jī)制有了一個(gè)非常深入的理解了蔑祟,如果對(duì)文中內(nèi)容有疑惑的話請(qǐng)?jiān)诓┛拖路搅粞猿煤模視?huì)盡可能地解答,謝謝大家疆虚,望大家多多支持苛败!