在使用Handler的過(guò)程中主要涉及到以下幾個(gè)類Looper、Handler、Message、還有一個(gè)隱藏的Message Queue,它直接與Looper交互,我們不會(huì)直接接觸。
Handler創(chuàng)建
Handler#Handler
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());
}
}
//關(guān)聯(lián)Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//關(guān)聯(lián)MessageQueue 同時(shí)也說(shuō)明了MessageQueue 屬于Looper
mQueue = mLooper.mQueue;
//注意這里的mCallback 它也是消息處理方式的一種,下文會(huì)有分析
mCallback = callback;
mAsynchronous = async;
}
上面的代碼展示了Handler如何與Looper、MessageQueue關(guān)聯(lián),下面我們看下Looper是如何被創(chuàng)建得的,以及它的MessageQueue是怎么創(chuàng)建的。
Looper#myLooper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到Looper 是通過(guò)ThreadLocal.get得到的,那ThreadLocal又是什么呢?
通過(guò)注釋我們可以發(fā)現(xiàn),ThreadLocal是一個(gè)跟線程綁定的數(shù)據(jù)存儲(chǔ)類,它可以在指定的線程中存儲(chǔ)數(shù)據(jù),同時(shí)也只能在指定線程中才能獲取數(shù)據(jù),對(duì)于其他線程來(lái)時(shí)是無(wú)效的,既然是集合肯定有set和get方法。
首先我們來(lái)看下線程和Loop之間的關(guān)系圖:
下面我們來(lái)看下
ThreadLocal#set
public void set(T value) {
//得到當(dāng)前正在運(yùn)行的線程
Thread t = Thread.currentThread();
//ThreadLocalMap 是一個(gè)自定義的hash map 用來(lái)存儲(chǔ)數(shù)據(jù)
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我們說(shuō)過(guò)ThreadLocal是跟指定線程綁定的,其實(shí)從下面代碼就能看出來(lái)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
代碼很簡(jiǎn)單,我就不解釋了。
好了既然我們知道了ThreadLocal,那接下來(lái)我們就看下Looper。既然ThreadLocal.get()得到的是Looper,我們就理由相信這個(gè)Looper是跟UI線程綁定的。可是這個(gè)Looper又是在哪初始化的呢? 而且初始化肯定是通過(guò)ThreadLocal.set方式調(diào)用的。
Looper#prepare
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
//創(chuàng)建Looper的同時(shí)創(chuàng)建了MessageQueue
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
那Looper#prepare又是在哪被調(diào)用了呢?最終我們?cè)贏ctivityThread#main函數(shù)中找到了,也就是程序啟動(dòng)時(shí)調(diào)用的。
ActivityThread#main
public static void main(String[] args) {
//............. 無(wú)關(guān)代碼...............
此時(shí)跟UI線程綁定的Looper已經(jīng)創(chuàng)建了
Looper.prepareMainLooper();
//開(kāi)啟無(wú)線循環(huán)來(lái)不斷的從消息隊(duì)列中拿消息
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper#loop(省略一部分代碼)
public static void loop() {
//獲得一個(gè)跟UI線程綁定的 Looper 對(duì)象
final Looper me = myLooper();
// 拿到 looper 對(duì)應(yīng)的 mQueue 對(duì)象
final MessageQueue queue = me.mQueue;
//死循環(huán)監(jiān)聽(tīng)(如果沒(méi)有消息變化,他不會(huì)工作的) 不斷輪訓(xùn) queue 中的 Message
for (;;) {
// 通過(guò) queue 的 next 方法拿到一個(gè) Message
Message msg = queue.next(); // might block
//空判斷
if (msg == null)return;
//消息分發(fā) 此處的target其實(shí)就是綁定的Handler
msg.target.dispatchMessage(msg);
//回收操作
msg.recycleUnchecked();
}
}
到這里的時(shí)候我先你的腦子肯定會(huì)閃過(guò)一個(gè)念頭進(jìn)入死循環(huán)那程序不就卡死了嗎,程序還在嗎執(zhí)行呢?這個(gè)問(wèn)題我們先放放,下面會(huì)簡(jiǎn)答,這里可以先說(shuō)結(jié)論。首先MessageQueue不是傳統(tǒng)的阻塞隊(duì)列,因?yàn)樗鼪](méi)有繼承任何隊(duì)列,同時(shí)內(nèi)部也沒(méi)有持有任何阻塞隊(duì)列的對(duì)象,那它是如何實(shí)現(xiàn)阻塞隊(duì)列的效果的呢?其實(shí)這里使用了linux的epoll技術(shù),感興趣的朋友可以深入研究下,下次如果有時(shí)間的話我也會(huì)寫一篇相關(guān)的博文來(lái)介紹。下面是摘自百度的epoll介紹:
epoll是Linux內(nèi)核為處理大批量文件描述符而作了改進(jìn)的poll,是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本,它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率。另一點(diǎn)原因就是獲取事件的時(shí)候,它無(wú)須遍歷整個(gè)被偵聽(tīng)的描述符集,只要遍歷那些被內(nèi)核IO事件異步喚醒而加入Ready隊(duì)列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(fā)(Level Triggered)外,還提供了邊緣觸發(fā)(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態(tài),減少epoll_wait/epoll_pwait的調(diào)用,提高應(yīng)用程序效率
Handler創(chuàng)建的另一種姿勢(shì)
上文我們分析了在UI線程(主線程)中創(chuàng)建的Handler,并會(huì)得到一個(gè)UI Looper,那假如我們新建一個(gè)線程并在其中創(chuàng)建Handler會(huì)發(fā)生什么?
public class LooperThread extends Thread {
private Handler handler1;
private Handler handler2;
@Override
public void run() {
// 將當(dāng)前線程初始化為L(zhǎng)ooper線程
Looper.prepare();
// 實(shí)例化兩個(gè)handler
handler1 = new Handler();
handler2 = new Handler();
// 開(kāi)始循環(huán)處理消息隊(duì)列
Looper.loop();
}
}
在子線程中我們可以創(chuàng)建多個(gè)Handler,但是必須手動(dòng)調(diào)用Looper.prepare();和Looper.loop();而且Looper.prepare()必須在Handler創(chuàng)建之前,這是為什么呢?我們回到上文中的代碼。因?yàn)樵趧?chuàng)建Handler的時(shí)候會(huì)檢查mLooper 是否為null,為null會(huì)拋出異常,并且此處的Looper是跟當(dāng)前線程綁定的。
public Handler(Callback callback, boolean async) {
//關(guān)聯(lián)Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
}
要保證mLooper 不為null,必須要先調(diào)用Looper.prepare()進(jìn)行Looper的創(chuàng)建并綁定當(dāng)前線程。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
在創(chuàng)建完Handler之后還需要手動(dòng)調(diào)用Looper.loop()開(kāi)啟消息循環(huán)隊(duì)列。
階段總結(jié)
好了到這,我們已經(jīng)分析完Handler以及Looper和MessageQueue的創(chuàng)建和關(guān)聯(lián),并且還知道,創(chuàng)建完Looper和MessageQueue之后會(huì)進(jìn)入一個(gè)死循環(huán)一直等待消息的到來(lái)并拿出消息進(jìn)行分發(fā)處理,否則會(huì)一直阻塞,其中這里的阻塞隊(duì)列利用了linux的epoll技術(shù)。另外,我們還知道一個(gè)線程可以有多個(gè)Handler,但是只能有一個(gè)Looper!
Handler發(fā)送消息
有了handler之后,我們就可以使用 post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long)和 sendMessageDelayed(Message, long)這些方法向MQ上發(fā)送消息了。從上面這些方法你可能會(huì)以為我們可以發(fā)送2中消息類型:Message和Runnable,但其實(shí)發(fā)出的Runnable最終也會(huì)被封裝成Message,下面我們來(lái)看代碼:
Handler#xxx
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//得到消息隊(duì)列
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);
}
可以看到不管通過(guò)哪種方法發(fā)送消息,最終都會(huì)進(jìn)入到sendMessageAtTime方法,并執(zhí)行enqueueMessage入隊(duì)操作。
Handler#enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//注意看這行代碼 我們將Handler賦值給Message的target
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
階段總結(jié)
通過(guò)上面的代碼得到一條原則就是:消息發(fā)送和處理遵循『誰(shuí)發(fā)送,誰(shuí)處理』的原則。過(guò)程還是比較簡(jiǎn)單的,下面有2點(diǎn)要注意的地方:
- 我們發(fā)送的2中類型消息最終都會(huì)被封裝成Message對(duì)象進(jìn)行發(fā)送
- 在創(chuàng)建完消息之后我們會(huì)將Message和Handler通過(guò)msg.target = this進(jìn)行綁定,方便下面進(jìn)行處理。
Handler處理消息
Looper#loop
public static void loop() {
//獲得一個(gè) Looper 對(duì)象
final Looper me = myLooper();
// 拿到 looper 對(duì)應(yīng)的 mQueue 對(duì)象
final MessageQueue queue = me.mQueue;
//死循環(huán)監(jiān)聽(tīng)(如果沒(méi)有消息變化,他不會(huì)工作的) 不斷輪訓(xùn) queue 中的 Message
for (;;) {
// 通過(guò) queue 的 next 方法拿到一個(gè) Message
Message msg = queue.next(); // might block
//空判斷
if (msg == null)return;
//消息分發(fā)
msg.target.dispatchMessage(msg);
//回收操作
msg.recycleUnchecked();
}
}
MessageQueue的工作方式是當(dāng)有消息被放入的時(shí)候MessageQueue.next()會(huì)返回Message對(duì)象,否則就會(huì)阻塞在這,拿到消息以后會(huì)調(diào)用Message綁定的Handler來(lái)出來(lái)消息,然后回回收消息。下面讓我們看下消息是如何被處理。
Handler#dispatchMessage
public void dispatchMessage(Message msg) {
//callback對(duì)應(yīng)Runnable對(duì)象
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();
}
public void handleMessage(Message msg) {
}
可以看到,消息處理分成了2中方式,一種是我們傳遞的Runnable對(duì)象,另一種是普通的Message對(duì)象。代碼很簡(jiǎn)單,handleCallback直接調(diào)用了Runnable.run,而handleMessage是空實(shí)現(xiàn),需要我們重寫并且實(shí)現(xiàn)它。還有一種處理方式就是mCallback,是在創(chuàng)建Handler的時(shí)候通過(guò)都找參數(shù)傳入的,大家回去最上面通過(guò)注釋可以看到。
階段總結(jié)
通過(guò)上面得到代碼,我們了解到在處理消息的時(shí)候有三種方式,并且是有順序的。
- 如果有Runnable消息就直接處理Runnable消息,然后忽略其他消息,所以Runnable的優(yōu)先級(jí)是最高的。
- 沒(méi)有Runnable消息,查找時(shí)候有通過(guò)構(gòu)造函數(shù)傳入的Callback對(duì)象,有就處理并檢查時(shí)候處理成功,成功就直接returan,否則才調(diào)用最普通的Message對(duì)象處理。
- 普通Message的優(yōu)先級(jí)是最低的,并且需要我們自己來(lái)實(shí)現(xiàn)handleMessage方法。
- 從上面的代碼中我們也驗(yàn)證了Handler誰(shuí)發(fā)送就誰(shuí)處理的原則,實(shí)現(xiàn)方式是通過(guò)將Handler賦值給Message.target來(lái)實(shí)現(xiàn)的。
epoll的真相
到此關(guān)于Handler的創(chuàng)建、消息發(fā)送以及消息處理都分析完畢了。現(xiàn)在還剩下那個(gè)死循環(huán)的問(wèn)題一直困擾著我們,那就是linux底層epoll到底是如何處理消息的呢。本來(lái)這段代碼想自己分析的,但在查找資料的時(shí)候發(fā)現(xiàn)已經(jīng)有好多人分析過(guò)了,并且分析的比較透徹,其中MessageQueue涉及到很多native方法,我這里就不分析,下面放上2篇分析的比較好的博文供大家自己來(lái)參考。
深入理解 MessageQueue
Looper 中的 loop() 方法是如何實(shí)現(xiàn)阻塞的