Android消息機制:Handler、Looper、MessageQueue對比分析

Android消息機制:Handler、Looper、MessageQueue對比分析,在Andrid啟動的時候,默認會有一個主線程,也就是UI線程,在Android3.0之後的版本中,Android禁止在主線程中進行網絡請求,通常情況下網絡請求會一個極其耗時的操作,所以耗時操作會嚴重影響用戶的體驗,甚至會出現ANR,但是有一個需求永遠存在,就是在耗時操作之後,把耗時操作之後的結果更新到界面上來。一般情況下,可以這麼用:

new Thread(new Runnable(){
    @override
    public void run(){
        //這個是隨意定義的耗時操作
        String response = HttpUtils.post(url,map);
        //這個時候想把獲取的response顯示到界面上去,但是隻有UI線程才能更新UI界面
        //而且這個子線程獲取到的response與顯示界面的操作得同步,我們可以這麼用
        runOnUiThread(new Runnable(){
            @override
            public void run(){
                //將網絡獲取到的結果顯示到UI界面上去
                mTvDisplay.setText(response);
            }
        });
    }
}).start();

runOnUiThread()方法底層則是調用瞭Android提供的異步消息處理機制Handler,該方法是對異步消息處理機制的一個封裝,這個異步消息處理機制Handler處理消息的過程可以簡單地分為:
Message、Handler、MessageQueue、Looper,他們的關系如下:

Handler

這張圖可以一目瞭然地將他們之間的關系描述得非常清楚,他們的處理過程是這樣的:
UI線程的消息循環是在ActivityThread.main()方法中創建的,源碼如下:

public static void main(String[] args){
    ...
    Looper.prepareMainLooper();//創建消息循環Looper
    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if(sMainThreadHandler == null){
        sMainThreadHandler = thread.getHandler();//UI線程的Handler
    }
    ...
    Looper.loop();//執行消息循環
}

開始執行從main()方法開始,如何將Message投遞到MessageQueue中,如何將Message從MessageQueue中取出,這其中的關鍵就是Handler。其實每一個Handler都會關聯一個消息隊列,消息隊列被封裝在Looper中,而每個Looper又會關聯一個線程(Looper通過ThreadLocal封裝),最終就等於每個消息隊列會關聯一個線程。默認情況下,消息隊列隻有一個,就是UI線程的消息隊列,它是在main方法中被 創建的,Looper.prepareMainLooper()方法來創建,Looper.loop()來啟動消息循環,Handler是如何關聯消息隊列以及線程的,看源碼:

public Handler(){
    ...
    mLooper = Looper.myLooper();//獲取Looper
    if(mLooper == null){
        //throw Exeception;
    }
    mQueue = mLooper.mQueue;//獲取消息隊列
    mCallback = null;
}

public static Looper myLooper(){
    return sThreadLocal.get();
}

//設置UI線程的Looper
public static void prepareMainLooper(){
    prepare();
    setMainLooper(myLooper());
    myLooper().mQueue.mQuitAllowed = false;
}

private synchroinzed static void setMainLooper(Looper looper){
    mMainLooper = looper;
}

//為當前線程設置一個Looper
public static void prepare(){
    if(sThreadLocal.get() != null){
        //throw Exception
    }
    sThreadLocal.set(new Looper());
}

Handler的構造函數中看到,Looper是通過Looper.myLooper()來獲取的,然後看myLooper()方法,直接通過sThreadLocal.get()方法獲取的,那麼既然能夠獲取就肯定已經存儲在裡面瞭,什麼時候把Looper存進去呢,看prepareMainLooper()方法,調用瞭prepare()方法,在prepare()方法的末端,直接new Thread()存儲在瞭sThreadLocal中,到這裡,隊列就跟線程關聯上瞭,代表著不同的線程就不能訪問別的消息隊列。

回到Handler中來,消息隊列通過Looper與線程關聯上,而Handler又與Looper關聯,因此Handler最終就和線程、線程的消息隊列關聯上瞭。所以,更新UI的Handler必須i要在主線程中創建,因為Handler要與主線程的消息隊列關聯上,這樣handleMessage才會執行在UI線程。

Looper怎麼創建的知道瞭,Looper怎麼循環起來的呢,是通過looper()方法,源碼如下:

public static void loop(){
    Looper me = myLooper();
    if(me == null){
        //throw Exception
    }
    MessageQueue queue = me.mQueue;//獲取消息隊列
    ...
    while(true){
        Message msg = queue.next();//不斷獲取消息
        if(msg != null){
            if(msg.target == null){
                return;
            }
            ...
            msg.target.dispatchMessage(msg);//處理消息
            msg.recycle();
        }
    }
}

一個死循環,代表著不斷從MessageQueue中取消息,對於Looper的步驟大概就是以下兩步:

1.通過Looper.prepare()來創建Looper對象,消息隊列在Looper對象中,存儲在sThreadLocal
2.通過Looper.loop()來啟動消息循環

從上述的loop()源碼中可以看出,最最最有用的一行代碼就是msg.target.dispatchMessage(msg),那麼
target是什麼,dispatchMessage(msg)又拿著這個msg去幹瞭什麼事,接下來繼續看Message的成員變量:

public final class Message implements Parcelable{
    Handler target;
    Runnable callback;
    Message next;
    ...
}

顯然target是Handler類的對象,那麼dispatchMessage(msg)就是Handler拿著去幹瞭什麼,看Handler其中的幾個方法:

//一般new的Handler都會重寫這個方法,用來實現自己的自定義業務
public void handleMessage(Message msg){
}

private final void handleCallback(Message message){
    message.callback.run();
}

public void dispatchMessage(message msg){
    if(msg.callback != null){
        handleCallback(msg);
    } else {
        if(mCallback != null){
            if(mCallback.handleMessage(msg)){
                return;
            }
        }
        handleMessage(msg);
    }
}

上述這段代碼的意思就是,sHandler.post(new Runnable()),那麼就直接調用這個Runnable的run(),如果是直接sendMessage(msg)的話,就直接調用重寫的handleMessage(),這裡可以看一下post()和sendMessage()的兩種實現,先看post():

public final boolean post(Runnable r){
    return sendMessageDelayed(getPostMessage(r),0);
}

private final Message getPostMessage(Runnable r){
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

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){
    boolean sent = false;
    MessageQueue queue = mQueue;
    if(queue != null){
        msg.target = this;//將target設置為當前的Handler對象
        sent = queue.enqueueMessage(msg,uptimeMillis);//將消息插入到消息隊列
    } else {
        ...
    }
    return sent;
}

從上面可以看出,Runnable被設置到瞭Message的Runnable中去瞭,然後就直接把Message扔到瞭MessageQueue中,再看sendMessage();

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg,0);
}

由此可見,無論是post()方法還是sendMessage()方法,他們內部都是經過一步一步的調用,調用到瞭sendMessageDelayed()這個方法,然後通過打包成Message再把他們扔進瞭MessageQueue中,然後Looper從MessageQueue不斷讀取消息,然後扔給瞭Message.target也就是Handler的dispatchMessage()這個方法不斷去處理消息,無論是post的Runnable還是handleMessage的處理方式,經過dispatchMessage中的判定去執行。

所以!

發送消息是Handler的post()方法和sendMessage()方法,
處理消息的還是Handler的handleMessage()handleCallback()(通過Runnable中的run()方法去執行),
由此可見,有瞭Handler,消息循環才能運轉起來。

runOnUiThread()的實現過程大概是這樣:

事件響應→開啟線程→runOnUiThread→判斷是否當前線程為主UI線程(是就立刻執行,不是就通過handler.post()發送到動作序列中,等到是主UI線程再立刻執行)
Handler的實現過程大概是這樣:
事件響應→開啟線程→new Message()→handler.sendMessage(msg);→new handler(msg.getdata→getString→更改UI)

由此可見,通過Handler可以更加精細地控制自己的業務邏輯更好的在一些復雜的情況下進行合理的操作。

Android的異步消息機制大概就這樣,過程使用,源碼分析,如有不對指出望請指出批評,謝謝。

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *