1. 程式人生 > >[圖解法結合原始碼]理解、記憶Handler、Looper、MessageQueue之間的關係

[圖解法結合原始碼]理解、記憶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()方法的原始碼:

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,那麼將直接丟擲異常。這是基本法,沒得說。

然後我們再來說說一個執行緒一般要怎麼建立一個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之前。

如果沒看懂,留個言?