前言
這篇博客將會涉及以下內(nèi)容:
1、消息機(jī)制概述
2、UML圖解消息機(jī)制相關(guān)類
3、從在主線程更新UI的方法帶你暢游消息機(jī)制的源碼,更加方便自己理解
4、Handler
5、Looper
6、MessageQueue和Message
7、消息機(jī)制的應(yīng)用
消息機(jī)制概述
Android 系統(tǒng)在設(shè)計的初期就已經(jīng)考慮到了 UI 的更新問題,由于 Android 中的 View 是線程不安全的,然而程序中異步處理任務(wù)結(jié)束后更新 UI 元素也是必須的。這就造成了一個矛盾,最簡單的解決方法肯定是給 View 加同步鎖使其變成線程安全的。這樣做不是不可以,只是會有兩點壞處:
1、加鎖會有導(dǎo)致效率底下
2、由于可以在多個地方更新 UI,開發(fā)就必須很小心操作,開發(fā)起來就很麻煩,一不小心就出錯了。
基于以上兩個缺點,這種方式被拋棄。于是機(jī)智如我谷歌。。。設(shè)置一個線程專門處理 UI控件的更新,如果其他線程也需要對 UI 進(jìn)行更新,不好意思,您把您想做的告訴那個專門處理 UI 線程的家伙,讓它幫你做。
那么您也看出來了,消息機(jī)制其實可以很簡單的用一句話概括,就是:其他線程通過給特定線程發(fā)送消息,將某項專職的工作,交給這個特定的線程去做。比如說其他線程都把處理 UI 顯示的工作通過發(fā)送消息交給 UI 線程去做。
實現(xiàn)的原理呢,我是這么理解的,現(xiàn)在要做的主要工作就是切換線程去操作,怎么切換呢?把兩條線程看作是兩條并行的公路,如果要從一條公路轉(zhuǎn)到另一條公路上,要怎么做呢?是不是只要找到兩條公路交叉或者共用某個資源的地方,如果說交叉路口,比如說加油站。當(dāng)然了,線程是不存在交叉的地方的,那么可以考慮他們公用資源的地方,不同的進(jìn)程享用不同的內(nèi)存空間,但是同一個進(jìn)程的不同線程享用的是同一片內(nèi)存空間,那讓其他線程把要處理的消息放到這個特定的內(nèi)存空間上,處理消息的線程來這個內(nèi)存空間上來取消息去處理不就可以了嗎。事實正是如此,在 Android 的消息機(jī)制中,扮演這個特定內(nèi)存空間的對象就是 MessageQueue 對象,發(fā)送和處理的消息就是 Message 對象。其他的Handler 和 Looper 都是為了配合線程切換用的。
這樣理解起來是不是就是 so easy 了呢?
UML圖解消息機(jī)制相關(guān)類
不知道上面的說法您是否可以對消息機(jī)制有了一個基本的認(rèn)知呢?用類圖來對消息機(jī)制中涉及到的幾個類有一個概括的認(rèn)識;通過時序圖,可以很清晰的觀察到整個消息機(jī)制的處理過程。
消息主要設(shè)計到下面幾個類:
Handler:這是消息的發(fā)出的地方,也是消息處理的地方。
Looper:這是檢測消息的地方。
MessageQueue: 這是存放消息的地方,Handler 把消息發(fā)到了這里,Looper 從這里取出消息交給 Handler 進(jìn)行處理
Message:嗚嗚嗚…他們發(fā)的是我,處理的也是我。
Thread:我在這里專門指代的是,處理消息的線程。消息的發(fā)送是在別的線程。
話不多說,先來看一張圖(UML 忘的差不多了,剛補(bǔ)的,如果有錯誤,麻煩大家指出)
暢游源碼
圖在這里了,怎么看呢,整個消息機(jī)制相關(guān)的類密密麻麻支撐了一張網(wǎng),咋個看嘛......不急不急,咱們先來思考一下咱們常用的更新UI是怎么一個操作步驟。
在主線程新建一個 Handler 對象,在構(gòu)造方法中傳入一個實現(xiàn) Handler.Callback 接口的匿名類的對象,實現(xiàn)接口中的 handleMessage 方法
在非 UI 線程使用 Handler 的 sendMessage 或者 post 方法發(fā)送一個消息
然后 handleMessage 方法會在不久的將來馬上執(zhí)行,實現(xiàn)更新 UI 的操作。
那咱們就跟著這個思路來看一看這張圖,先看 Handler 類,你會發(fā)現(xiàn),Handler 真的是個相當(dāng)關(guān)鍵的核心(當(dāng)然,其他部分也是不可或缺的),他幾乎擁有所有其他相關(guān)對象的引用。
Handler 擁有 Looper 的引用,通過得到 Looper 對象獲得 Looper 中保存的 MessageQueue 對象
Handler 擁有 MessageQueue 的引用,使 Handler 得以擁有發(fā)送消息(將 Message 放入 MessageQueue )的能力
Handler 擁有 Handler.Callback 的引用,使得 Handler 可以方便的進(jìn)行消息的處理。
來思考一個問題:為什么 Handler 在其他線程發(fā)送消息之后,就跑到了主線程的 handleMessage方 法中去更新 UI ?
這個問題暫時先放著,等下回過頭再來看。
我們現(xiàn)在先跟著第1,2,3步看看系統(tǒng)都幫我們做了什么操作呢?這就是在看源碼,不要覺得很高深。下面是鮮活的代碼,為了方便您查看,我?guī)湍鰜砹?。如果有興趣,您也可以在AS里點開看看
//這是在主線程中
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 1:
Toast.makeText(mContext, "你真漂亮", Toast.LENGTH_SHORT).show(); break;
case 2:
Toast.makeText(mContext, "你也很帥呢", Toast.LENGTH_SHORT).show(); break;
default:
break;
}
return false;
}
});
//這是我們在主線程中創(chuàng)建Handler時會使用的構(gòu)造方法
public Handler(Callback callback) {
this(callback, false);//調(diào)用了下面的這個構(gòu)造方法↓
} //先不要管第二個參數(shù)。跟緊主線,別跟丟了
public Handler(Callback callback, boolean async) {
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());
}
}
//在這里獲取到Looper對象,怎么獲取的,稍后再看
mLooper = Looper.myLooper();
//如果獲取的mLooper為空,直接拋出異常,說你不能在一個沒有調(diào)用Looper.prepare()方法
//的線程里創(chuàng)建Handler
//如此看來,Looper.prepare()方法重要的嘞
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()"); }
//通過mLooper對象獲取MessageQueue這個消息隊列(單鏈表)
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
到此為止,一個 Handler 就創(chuàng)建好了,(還有一個問題是 Looper.prepare 方法很重要,但是我們還沒有去考慮他是干嘛的,不急不急,先順著一條線看,不然看源代碼的過程會把你搞死翹翹的)先面就該進(jìn)行第二步,看看 Handler.sendMessage 干了啥,代碼段又來嘍!
//在一個新建的線程里使用創(chuàng)建好的Handler發(fā)送一個消息
new Thread(new Runnable() {
@Override public void run() {
//在這兒干點你想干的吧,一些耗時的計算或者網(wǎng)絡(luò)操作啥的
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
}).start(); //直接調(diào)用的是這個函數(shù)
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0); }
//轉(zhuǎn)而調(diào)用了這個函數(shù)
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } //轉(zhuǎn)而又來到了這里 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) {
//target就是Message綁定的Handler,看看類圖,上面有這個細(xì)節(jié) msg.target = this; if (mAsynchronous) {
msg.setAsynchronous(true); }
//最后的最后,調(diào)用了MessageQueue的enqueueMessage方法
return queue.enqueueMessage(msg, uptimeMillis); }
//再看一下MessageQueue的enqueueMessage方法,
//我把其他一些無關(guān)的細(xì)節(jié)給刪掉了,只為了更加容易閱讀
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
Message p = mMessages;
if (p == null) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
} else {
//下面的錯誤就是遍歷單鏈表,找到鏈表的尾部,這個沒有難度的吧? Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
//找到了尾部,現(xiàn)在的結(jié)構(gòu)是這樣的。
//A->B->C...->pre(p)->null
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;//把next插入鏈表的尾部
}
}
return true; }
到此為止,第二步就結(jié)束了,成功把一個消息插入到了 MessageQueue 的尾部??墒悄愫芸炀蜁l(fā)現(xiàn),第三步好像從這條路探尋不下去了。接下來就等著別人來調(diào)用 Handler 中的方法了,可是是誰調(diào)用的,在哪兒調(diào)用的?我們現(xiàn)在好像毫無頭緒了?怎么辦?怎么辦?我們剛才不是看到一個 Looper.myLooper() 和 Looper.prepare() 方法,說是很重要但是一直沒看嗎,既然現(xiàn)在搜尋不下去了,是不是可以回頭看看了?還有一點,Looper , 看起來是在循環(huán),循環(huán)什么玩意兒呢?我們?nèi)ズ煤每纯?Looper 類吧。 一共就三百來行代碼,仔細(xì)看看,你會發(fā)現(xiàn)有一個核心方法:Looper.loop(); 同樣的,我把影響閱讀的非主線代碼剔除了,發(fā)現(xiàn) Looper.loop 方法就長這樣:
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return; }
try {
msg.target.dispatchMessage(msg);
} finally {
}
}
}
清晰可見的是,Looper.loop() 方法一直遍歷 MessqgeQueue,阻塞線程,直到獲取到一個Message ,然后調(diào)用了 Message 的一個成員變量 target( 其實就是 Handler )的 dispatchMessage(msg) 方法,嗨,還真的又跟 Handler 扯上關(guān)系了,既然這里扯上關(guān)系了,而且還是一個分發(fā)消息的方法,可以大膽猜測就是讓Handler去處理這個消息的。
那么我們來看看這個方法:
/** * Handle system messages here. * 如果Message中callback對象不為空(這是調(diào)用handler.post(Runnable)方法發(fā)送的消息), * 就調(diào)用callback的run方法 * 否則如果創(chuàng)建Handler的時候如果設(shè)置了Callback就調(diào)用創(chuàng)建時候的傳入的 * 實現(xiàn)Handler.Callback接口的類的對象的handleMessage方法,看這就是回調(diào)方法被調(diào)用的地方。 * 再如果沒有mCallback對象,就調(diào)用自身的handleMessage方法,為了Handler的子類復(fù)寫了該方法的時候,方便調(diào)用,如,IntentService里的ServiceHandler就是繼承自Handler的,并且重寫了handleMessage方法。 */
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) { message.callback.run(); }
//ServiceHandler繼承自Service并且重寫了handleMessage方法
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper); }
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1); }
}
到了這里,三步走已經(jīng)看完了,我想消息機(jī)制在我們心里已經(jīng)又清晰了一層,但是不用急,咱們前邊提的一個問題不是還沒有解決嗎,先把他解決掉吧,一起繼續(xù)來看源碼。
到了這里,三步走已經(jīng)看完了,我想消息機(jī)制在我們心里已經(jīng)又清晰了一層,但是不用急,咱們前邊提的一個問題不是還沒有解決嗎,先把他解決掉吧,一起繼續(xù)來看源碼。
咱們現(xiàn)在已知的是這樣的,在主線程創(chuàng)建的Handler發(fā)送了一個消息,發(fā)送消息的代碼運(yùn)行在其他線程,將代碼加入消息隊列也是在其他線程(加了線程同步鎖)。然后 handleMessage 發(fā)生在主線程,那么調(diào)用該方法的 dispatchMessage 方法也是運(yùn)行在主線程的,dispatchMessage 是在 Looper.loop 方法中調(diào)用的,也就是說loop方法也運(yùn)行在主線程,那么問題就明朗了,可是 loop 方法是誰調(diào)用的,在哪里調(diào)用的呢?當(dāng)然是系統(tǒng)啟動的時候創(chuàng)建主線程之后再主線程的 run 方法中調(diào)用了 Looper.prepare 和 Looper.loop 方法,但是這點我還沒看,留著以后再看吧。
通過上面的分析,我們是不是可以自己來試著建立這樣一個模型:
創(chuàng)建一個線程 A
在這個線程的 run 方法中調(diào)用 Looper.prepare 和 Looper.loop 方法使該線程阻塞,等待消息發(fā)過來,然后處理
在該線程中創(chuàng)建一個 Hanlder,用來處理 looper 發(fā)送來的待處理的消息
創(chuàng)建一些其他的線程a、b、c,做一點操作之后,通過 Handler 把消息傳遞出去,讓線程 A 去處理。
public class MyThread extends Thread {
private static final String TAG = "MyThread";
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i(TAG,"你真漂亮");
break;
case 2:
Log.i(TAG,"你也很帥呢");
break;
default:
break;
}
return false;
}
});
public Handler getHandler() {
return handler;
}
@Override
public void run() {
super.run();
Looper.prepare();
Looper.loop();
}
@Override
public void destroy() {
super.destroy();
Looper.myLooper().quit();
}
}// private void testMyThread() {
MyThread thread = new MyThread();
thread.start();
final Handler handler = thread.getHandler();
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
new Thread(new Runnable() {
@Override
public void run() {
try {
sleep(400);
Message message = new Message();
message.what = 2;
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); }
試著想一想,如果把線程 A 看成主線程,在回調(diào)方法更新 UI,那這不就是 Android 系統(tǒng)中更新 UI 使用的套路嗎?不錯,事實本就如此,工具是工具,用它來更新 UI 是可以的,那你當(dāng)然也可以用來做一些其他的工作啊。
在這里,通過以上的分析,不難得到下面的這個整個消息機(jī)制運(yùn)行過程的時序圖:
ok 啦,源碼閱讀到此為止。其他的細(xì)節(jié),有興趣的可以再細(xì)細(xì)研究一下。
下面我們來對涉及到的類進(jìn)行一下總結(jié)。
Handler
handler在消息機(jī)制中,扮演的是消息的發(fā)送方和處理方(通過回調(diào)函數(shù))。消息在一個線程通過 Handler 發(fā)送到 MessageQueue 中。Looper 獲取到 Message 之后,根據(jù) Message 中保存的 handler 對象調(diào)用 handler 對象的 dispatch 方法,進(jìn)行消息的處理。
Looper
Looper 在這里扮演的是一個輪詢消息隊列的角色,以為不停地去問 MessageQueue 要消息,得到消息之后,根據(jù) Message 中保存的 handler 對象調(diào)用 handler 對象的 dispatch 方法,進(jìn)行消息的處理。
MessageQueue 和 Message
MessageQueue 實質(zhì)上是一個單鏈表的結(jié)構(gòu),里面以鏈表的形式保存著 Handler 發(fā)送過來的消息,當(dāng)有新消息發(fā)來時放在鏈表的尾部,Looper 要取消息的時候從鏈表的頭部取出消息返回給 Looper 處理。Message 對象中保存在要處理的信息,同時也持有消息發(fā)送方(Handler)的引用,Looper 在得到該 Message 的時候,可以從 Message 中拿到消息的發(fā)送方,調(diào)用發(fā)送方的回調(diào)方法將消息傳遞過去交給 Handler 進(jìn)行處理。
消息機(jī)制的應(yīng)用
在 Android 中有很多消息機(jī)制的應(yīng)用,如:
UI 的更新
HandlerThread
IntentService
UI 的更新
UI 線程持有一個 Looper 對象,Looper 對象的 loop 方法在 UI 線程中一直不停的進(jìn)行死循環(huán),直到有新的消息發(fā)來的時候,交給特定的組件進(jìn)行處理,當(dāng)然了這個處理也是在主線程運(yùn)行的(如我們設(shè)置的點擊事件也是等著被 UI 線程調(diào)用的),正是由于這個原因,我們不能在主線程處理耗時操作。如果我們一個 View 的點擊事件里做了大量耗時的操作,由于這個操作也在主線程中運(yùn)行,主線程必須等著這個操作操作結(jié)束才能去處理其他的消息,這個時候表現(xiàn)的就是系統(tǒng)卡頓甚至報 ANR 的錯誤。
HandlerThread
HandlerThread 繼承自 Thread,內(nèi)部保存一個 Looper 對象。
這是一個系統(tǒng)幫我們包裝好的 Thread,這個線程的 run 方法已經(jīng)調(diào)用了 Looper.prepare 和Looper.loop(即已經(jīng)綁定了一個Looper對象,并且可以開始輪詢消息),創(chuàng)建該對象之后可以通過獲得對象獲取到一個 Looper 對象,將 Looper 對象傳遞給 Handler,完成 Handler和 Looper 以及 MessageQueue 的綁定。最后再其他的線程中調(diào)用 Handler 的 sendMessage 或者 post(Runable)方法發(fā)送消息,handler 中的 callback.handleMessage 方法會在 HandlerThread 中運(yùn)行。即,將消息發(fā)送到了特定的線程(此處是 HandlerThread)處理。
IntentService
IntentService 繼承自 Service,運(yùn)行時優(yōu)先級更高,內(nèi)部使用了 HandlerThread 作為處理消息的線程。內(nèi)部有一個私有內(nèi)部類 ServiceHandler 繼承自 Handler,并且會創(chuàng)建一個ServiceHandler 對象。
使用 startService()方法啟動 IntentService 時,不會重新創(chuàng)建一個服務(wù),會調(diào)用 ServiceHandler 對象發(fā)送包含該 Intent 的 Message 對象,該對象通過 HandlerThread 處理后交給ServiceHandler 重寫的 handleMessage 方法進(jìn)行處理,處理的方式是調(diào)用 IntentService 的 onHandleIntent(Intent)方法,所以使用的方式就是創(chuàng)建一個繼承自 IntentService 類的子類,并重寫 onHandleIntent 方法,在該方法中處理 startService 時傳遞的 Intent。Intent 中包含有要交給 Service 處理的信息。