[圖解法結合原始碼]理解、記憶Handler、Looper、MessageQueue之間的關係
>[圖解法結合原始碼理解、記憶Handler、Looper、MessageQueue之間的關係]
看了不少關於Handler、Looper、MessageQueue之間關係的文章。感覺挺枯燥的,上來就是一團程式碼,看著心煩。後來我捋了捋,畫了個圖。先看圖,我們再來談他們間的關係:
在這個圖中,我做了個類比:(很重要,多看幾遍)
MessageQueue,流水線上的"履帶";
Looper,履帶的"驅動輪";
Handler,流水線上的"工人";
Message,流水線上的"包裹"。
現在讓我們來解釋這三者間的工作關係,首先,我們要明確一個基本法:
一個Thread只能有且只能一個Looper。一個Looper只能對應一個Message。一個Looper和MessageQueue的繫結體可以對應多個Handler。(參考上圖)
1.Looper與MessageQueue
剛剛說了一個Thread只能有一個Looper。為什麼只能有一個呢?讓我們去看它的一個方法Looper.prepare()方法的原始碼:
第二行,如果當前執行緒已有一個Looper,那麼將直接丟擲異常。這是基本法,沒得說。public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(true)); }
然後我們再來說說一個執行緒一般要怎麼建立一個Looper,例子A:(下面會拿這個例子講解)
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // 你的方法 } }; } Looper.loop(); }
首先,一個執行緒先Looper.prepare(),建立一個Looper,在這個prepare()方法中,回頭看一眼基本法裡的程式碼,它會在 return 中new 一個(Looper(true));其實,就是下面的程式碼:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread(); //綁定當前執行緒
}
注意第二行,它建立並綁定了一個MessageQueue,做個類比,就是給Looper這個驅動輪套上了它的履帶MessageQueue,由於Looper在當前執行緒唯一,則其應為一一對應關係。
現在驅動輪有了,履帶有了,要讓這個流水線動起來,顯然,還需要另外一個操作。
沒錯,這個操作就是例子A中倒二行的Looper.loop()。其原始碼為:(僅列出你需要理解的程式碼,其他你不需要關心)
public static void loop() {
final Looper me = myLooper();//獲取當前執行緒的Looper
if (me == null) { //如果沒有為當前執行緒進行Looper.prepare(),跳出異常
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //獲得當前Looper me所繫結的MessageQueue
for (;;) { //無限迴圈
Message msg = queue.next(); // 通過MessageQueue的next()方法從隊首取一個訊息
if (msg == null) {
//如果訊息佇列為空,就退出這個無限迴圈
return;
}
msg.target.dispatchMessage(msg); //分發訊息,msg.target其實就是個handler,你先記著不要管
msg.recycle(); //通過MessageQueue的recycle()方法回收資源,讓"履帶"轉動取來
}
}
Tips:這裡需要注意一個事情,觀察到loop()方法裡有個無限迴圈。在例子A中,如果你在Looper.loop()後面寫程式碼,IDE會判斷這些程式碼是不會執行的,因此會報錯。也就說,loop()方法必須在該執行緒需要執行的內容的最後一行寫!!!
上面的loop()中我給出了詳細的註解,你稍微花兩分鐘就能看明白。第8行,從MessageQueue佇列中取走第一個訊息,然後在第13行,呼叫msg.target的disspachMessage()方法,分發這個訊息。其實msg.target就是個handler,至於為什麼,我會在Handler的部分進行講解,你先這個記著。
Looper部分總結一句話:
線上程的開頭,為這個執行緒準備一個Looper(Looper.prepare()),這個Looper會自動繫結上一個MessageQueue,然後線上程的最後,通過Looper.loop()方法,讓這個MessageQueue轉動起來,傳送這個MessageQueue上的訊息物件Message。每次Message被傳送到隊首,這個Message會被disspatchMessage()方法分發出去,分配到管理它的Handler(流水線工人)那裡進行處理。
2.Message
我們在圖中做了一個類比,把Message當成了一個包裹,那麼它裡面到底包裹了啥?我們來看一下它的原始碼:
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;//以上是一些資料型別,無視
public Messenger replyTo;
Bundle data;//一般用於封裝資料的包
Handler target;//注意看這裡,是個Handler
Runnable callback;//注意這個回撥
......
}
看到那個target了麼,就是個Handler。現在回去看我的那個圖,你會看到我在Handler("流水線工人")那裡寫了個蓋章二字,是什麼意思呢?
一個handler通過post方法(我們下面再說,總是就是執行緒把這個訊息Message丟給了Handler)拿到一個Message之後,這個Handler並不是馬上處理這個訊息,而是先蓋上一個章:
msg.target=this;
然後把這個訊息丟向流水線的"履帶"——MessageQueue,讓它排隊去,等它排到隊首之後,再通過msg.target.dispatchMessage()方法分到剛剛送它進流水線的“工人”手裡進行處理。
有的同學可能會問了,Handler拿到Message為什麼不馬上處理呢?
原因是:本身Handler並沒有提供併發解決機制,但MessageQueue的next()提供了併發解決機制。稍微理解一下這句話,很容易理解的。要知道Handler不只能接收到本執行緒丟過來的Msg包,還能接到其他執行緒(一般是子執行緒)丟過來的包(參考第一幅圖),你不搞個基本法,排隊來解決,這個程式怕是藥丸。
這裡還要注意一下那個Runnable callback的回撥。現在先別管,總之先記著,我挖的坑我肯定會填的。
Message要注意的只有兩種用法,一個是把資訊封裝,一個是解包提取資訊;
Message msg = Message.obtain();//儘量不要一直new Message(),原因很簡單,省記憶體。
Bundle b=new Bundle();//資訊封包
b.putInt("Yidong",10086);
msg.setData(b);
//msg.sendToTarget();
myHandler.post(msg);//先丟給流水線工人蓋章、入列
Bundle b=msg.getData();//在handlerMessage()方法中解包提取資訊。至於這個方法是啥,馬上就要說到了。
int a=b.getInt("Yidong");//a=10086
一句話總結,Message就是個"包裹",裡面裝了你需要傳遞資訊,然後被丟來丟去(╯‵□′)╯︵,最後被分配到它的工人那裡拆包進行處理。
3.Handler
終於,這篇文章要寫完了。QAQ
接下來,我們要說我們的主角了——Handler了。大部分時候,你不需要關心MessageQueue和Looper是怎麼工作的,但是Handler是你時時刻刻都必須打招呼的傢伙。
首先是Handler究竟是個什麼傢伙。我把它類比為流水線上"工人" ,它即負責把收到的訊息入列,也負責處理隊首屬於自己的那一份Message。
它的建構函式裡沒有啥東西,你只需要知道,它會先獲取當前執行緒的Looper並繫結,然後從這個Looper獲取到它的MessageQueue。然後,Handler與Looper、MessageQueue的關係就建立起來了。另外,他還建立了一個mCallback。這個待會兒再說。
剛剛我們一直在說msg.target.dispatchMessage()方法(其實也就是msg繫結的handler的disspatchMessage()),那麼這個方法到底是個啥東西呢?看原始碼:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
第2行,看到那個callback了沒有?是不是!很!熟!悉!我剛剛讓你注意了有木有?還記得它是啥麼?它其是個Runnable。
這一整段的程式碼就是告訴你:要分發這個程式碼,先看這個包裹的callback有沒有繫結上啥東西(一個Runable);如果有,直接交由這個Runable進行處理,如果沒有,檢視當前handler的mCallback有沒有繫結上啥東西;如果有,交由這個mCallback處理,如果沒有,那麼就呼叫當前handler的handleMessage()方法處理。
你可能要問,這個mCallback到底是個啥玩意?其實就是這個玩意:
public interface Callback {
public boolean handleMessage(Message msg);
}
講白了就是這個handler"工人"的上一個"工人","包裹"Message被丟來丟去地傳遞,可能它真正要處理它的工人並不是當前給它蓋章的工人,而是上一個.....不過,一般情況下我們並不會遇到這種情況。
現在要說的是handleMessage()方法,它的原始碼:
public void handleMessage(Message msg) {
//填入你自己的方法
}
臥槽?原始碼裡是空的?
當然了,這個handleMessage()方法就是這個"包裹"Message被丟來丟去後最後要被進行處理的地方(被丟給Runable處理的不算),你當然要重寫這個方法,給出你自己的處理方法了。
比如,把我上面寫的那個解包提取資訊的語句寫進去......
一句話總結:
Handler是整個工作流水線的"傳遞者"和Message"包裹"的"分發者"(比如甩給Runnable)、"處理者",是流水線的"工人們"。
好像,都寫完了?
其實並沒有,似乎,我們還有個Handler的post()方法沒說。這個方法,就是執行緒把"包裹"丟給Handler,讓Handler送其入列的方法。
4.Handler.post()
這個post到底有哪些方法呢?
public final boolean sendMessage(Message msg);//丟包法1,不吵吵直接丟
public final boolean sendEmptyMessageDelayed(int what, long delayMillis);//丟包法2,延遲一段時間後丟一個空包,只含一個空的資料what=0,告訴Looper:“嘿哥們你還沒空,繼續轉~”
public final boolean sendMessageDelayed(Message msg, long delayMillis);//丟包法3,延遲一段時間後丟
public boolean sendMessageAtTime(Message msg, long uptimeMillis);//丟包法4,在指定的時間丟
下面我會給出他們的原始碼,當然你要沒啥興趣讀,也沒關係,你可以直接看我給的結論,那就是
丟包法1中其實最後呼叫了丟包法2,2中調3,3中調4,4中呼叫了enqueueMessage(queue, msg, uptimeMillis),終於把這個包丟進了流水線"履帶"MessageQueue。在enqueueMessage()方法中,會進行msg.target=this的操作,也就是我們剛剛說的"蓋章"。
原始碼:
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
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) {
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);
}
這就是執行緒把資訊"包裹"Message靠"工人"Message送入列的方法了。
別急,別忘了,handler其實還有個"分發者"的身份,把資訊丟給Runnable處理的方法把?那麼是怎麼做的呢?其實一個Handler還有這個方法:
public final boolean post(Runnable r)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
是的,其實也就是把mCallback的值變成這個Runnable而已罷了...
你可能要問,我一個好好的Runnable,怎麼到了你Handler就變成一個Message了呢?
其實吧,Hander內部提供了getPostMessage方法把Runnable物件轉化為Message:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
好的,到了這裡,我們把這個利用Looper、MessageQueue、Handler進行訊息傳遞的流程總結一下:
一個執行緒,首先進行Looper.prepare(),就可以創建出一個綁定了MessageQueue"履帶"的[唯一]的Looper+MessageQueue"流水線";然後執行緒可以例項化出幾個Handler"工人"。執行緒有要處理的資訊"包裹"Message了,丟給對應的Handler"工人";這個工人判斷一下這個到底是個Runnable還是Message,如果是Runnable就包裝成一個Message,再"蓋章",然後丟向流水線,讓它排隊;不過不是,"蓋完章"不多bb直接甩進流水線。一個Message"包裹"到了流水線的隊首,就要被拿出來,根據剛剛蓋的章,各找各媽各回各家,該上哪上哪,然後進行msg.target.diapatchMessage()->msg.target.handleMessage()拆包處理。
看完這個總結,再去看我的圖,是不是理解了?
恩,其實還有一點:你看我給的圖的右邊,還有個Thread 2,裡面也站了一個Handler"工人",它負責把Thread 2要發給Thread 1的包裹丟進Thread 1的流水線。在編制上,他是Thread 1的"工人"(在Thread 1中例項化)。一般來說,Thread 2其實是 Thread 1的子執行緒。
為什麼說是子執行緒呢?廢話,要是Thread 1 在Thread 2 之前結束了,這名"工人"就被記憶體殺掉了,包要丟給誰?如果是子執行緒就放心,要麼一起死,要麼Thread 2死在 Thread 1之前。
如果沒看懂,留個言?