Android中的消息系統————Handler,MessageQueue與Looper

我們都知道,Android系統強制要求我們將更新ui等操作放在主線程中進行,而網絡請求,讀取文件等耗時操作則通常會放到子線程中運行,因此,在Android開發中經常需要在不同的線程之間進行切換。而Android系統爲我們提供了消息系統來進行異步消息的處理,因此我們有必要了解一下Android消息系統的工作原理。

Handler,MessageQueue與Looper之間的關係

我們先來看一下Handler,MessageQueue與Looper三者之間的關係。首先,Handler對象負責發出一個消息,這個消息最終會被提交到一個MessageQueue之中,這個MessageQueue則是一個專門用來存儲Message的隊列集合。而Looper對象內部有一個無限循環,它會不斷的從這個MessageQueue中取出消息,並將其交給發出該消息的Handler進行處理。

需要注意的是,我們可以在一個線程中創建很多個Handler對象,但是每個線程只會對應一個Looper和MessageQueue對象。Handler對象在初始化的時候會和該線程所對應的Looper及MessageQueue對象進行綁定。因此,Handler在哪個線程中創建,它發出的消息最終就會在哪個線程中執行。接下來我們通過源碼來詳細的看一下它們的工作原理。

消息系統的創建

先從消息系統的創建說起。Android中主線程的消息系統會在主線程啓動時默認被創建,而子線程的消息系統默認則不會被創建,我們需要在子線程中手動調用Looper.prepare()和Looper.loop()這兩個靜態方法纔可以開啓子線程的消息系統。接下來我們以主線程爲例看一下消息系統的創建過程。

Android主線程的消息系統是在ActivityThread類的main方法中被創建的,我們看一下main方法的代碼:

    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper(); //先調用prepareMainLooper方法

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();  //然後調用loop方法

        throw new RuntimeException("Main thread loop unexpectedly exited");//因爲Looper.loop()實際上是執行了一個無限循環,所以一般情況下不會走到這句,除非出現異常導致循環中斷
    }

我們可以看到,與子線程不同的是,主線程的消息系統在啓動時調用的是Looper.prepareMainLooper方法而非prepare方法。在調用完prepareMainLooper方法之後又調用了Looper.loop方法。我們看一下prepareMainLooper方法:

    public static void prepareMainLooper() {
        prepare(false);//調用prepare方法
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//通過myLooper方法將創建好的looper對象賦值給sMainLooper全局對象
        }
    }

可以看到prepareMianLooper方法中其實也是調用的prepare方法,prepare方法的源碼如下:

    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));
    }

在prepare方法中,系統直接通過new關鍵字創建了一個Looper對象,並將這個Looper對象放在了一個名爲sThreadLocal的全局對象中。

這個sThreadLocal對象是一個定義在Looper中的類型爲ThreadLocal的全局對象,並且被static final所修飾。ThreadLocal是java所提供的一個類,我們可以通過ThreadLocal的set(T value)方法來給這個ThreadLocal對象設置一個變量,但值得注意的是,通過ThreadLocal來維護的變量是線程私有的,各個線程通過ThreadLocal.get()方法取得的對象都是獨立的,他們之間的操作都互不影響的。主線程將一個Looper對象設置給了一個ThreadLocal,其他子線程是無法通過這個ThreadLocal對象來獲取主線程的Looper對象的。因此,Android通過ThreadLocal來維護Looper對象,就做到了每個線程對應一個獨立的Looper對象。

我們再來看一下Looper的構造方法:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

可見Looper對象在初始化時直接創建了一個MessageQueue集合,並賦值給成員變量mQueue。因此MessageQueue對象被Looper對象所持有。

現在Looper和MessageQueue對象已經創建完成了。我們再回到prepareMainLooper方法中。在通過prepare方法創建完Looper對象和MessageQueue對象後,系統又調用了Looper的myLooper方法,而myLooper方法返回的其實是剛纔創建的該線程所獨有的Looper對象,這裏即是主線程所對應的Looper對象。系統將這個對象賦值給了一個全局變量sMainLooper,方便之後使用getMainLooper方法來直接拿取主線程的Looper對象。myLooper方法的代碼如下:

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

至此準備工作都已經完成了,系統只需要再通過Looper.loop方法讓消息系統運行起來即可,loop方法的源碼如下:

    public static void loop() {
        final Looper me = myLooper();   //獲取looper對象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;  //獲取MessageQueue對象
        ...
        for (;;) {
            Message msg = queue.next(); // 從MessageQueue對象中獲取一個消息
            if (msg == null) {
                return;
            }
           ...
            try {
                msg.target.dispatchMessage(msg);//將這個消息分發給相應的Handler進行處理
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }

loop方法的代碼較多,爲了便於理解,省去了部分代碼。首先通過myLooper方法獲取當前線程對應的Looper對象,然後又通過me.mQueue拿取了looper對象內部的MessageQueue。之後開啓了一個無限循環,在循環中,首先通過queue.next()取出MessageQueue中存儲的一個消息,如果這個消息不爲null,則通過msg.target.dispatchMessage(msg)分發給相應的Handler進行處理。msg.target其實就是發出該消息的Handler對象。Handler在發出一個消息時會將自身存儲在Message內部的target變量中,之後在分析Handler時會講到。我們先來看一下dispatchMessage方法的源碼:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);    //通過handleCallback來執行Message對象內部的Runnable
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg); //將Message交給handleMessage方法進行處理
        }
    }

首先會檢查msg的callback對象是否爲null,這個callback是一個Runnable類型的對象,我們知道Handler可以發出兩種類型的消息,一種是通過sendMessage等方法直接發送一個Message消息對象,另一種是通過則會通過post方法發送一個Runnable對象。如果發送的是一個Runnable對象,Handler在內部也會將這個Runnable對象封裝成一個Message對象,並將原來的Runnable對象賦值給Message的callback變量。如果msg.callback不爲null,說明該消息原本是通過Handler的post方法發出的一個Runnable,那麼會通過handleCallback方法直接執行這個Runnable。如果msg.callback對象爲null,那麼就將這個msg交給Handler的handleMessage方法進行處理。我們在創建Handler對象時通常會重寫handleMessage方法來實現我們想要的邏輯。

由於loop方法內部其實是一個無限循環,因此Looper對象會不斷的從MessageQueue對象中拿取消息並分發給對應的Handler進行處理。需要注意的是,如果我們在子線程中調用了Looper.loop方法,那麼Looper中的無限循環會導致子線程阻塞,因此當我們在子線程中使用了Looper後,應該在適當的時機調用looper對象的quit或quitSafely方法來退出這個Looper。

至此整個主線程的消息系統已經創建完成並且開始工作了,接下來我們看一下Handler是如何將一個消息提交給相應的MessageQueue的。

消息的發送過程

Handler對象負責消息的發送和處理。我們先來看一下Handler對象的構造方法:

    public Handler(Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();//與Looper進行了綁定
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//與MessageQueue綁定
        mCallback = callback;  
        mAsynchronous = async;
    }

Handler對象有許多重載的構造方法,但這些構造方法最終都是調用的Hanler(Callback callback, boolean async)這個構造方法。在這個構造方法中,首先通過Looper.myLooper獲取到了一個Looper對象並賦值給了自己的一個成員變量,前面我們說過,myLooper對象返回的是當前線程所獨有的Looper對象,這樣一來Handler,Looper和線程之間就一一對應起來了。因此,無論handler在哪個線程發出消息,這個消息最終都會在handler初始化時所綁定的Looper所對應的線程中進行處理。

前面我們說過,Handler可以發出兩種類型的消息,一種是通過sendMessage方法發送一個Message對象,另一種是通過post方法發送一個Runnable。我們先來看一下sendMessage方法和post方法的源碼:

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

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

可以看到無論是post方法還是sendMessage方法最終都是調用的sendMessageDelayed方法,不同的是在post方法中先調用了getPostMessage方法來對Runnable對象進行了一些處理,我們來看一下getPostMessage方法:

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

正如我們前面所說的,Handler將通過post方法提交的Runnable封裝在了一個Message對象內的callback變量裏。接下來我們看一下sendMessageDelayed方法:

    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);
    }

可見在sendMessageDelayed方法中又調用了sendMessageAtTime方法。而在sendMessageAtTime方法中,先拿取了初始化時綁定的MessageQueue對象,然後將這個MessageQueue和Message對象一起傳給了enqueueMessage方法:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在enqueueMessage方法中,Handler將自身賦值給了Message的target變量,前面在講loop方法的時候也說過,Message最終會通過這個target變量來獲取對應的Handler,因此,Message最終會被髮出該消息的Handler所處理。之後又調用了MessageQueue的enqueueMessage方法,最終將這個Message提交給了對應的MessageQueue對象。MessageQueue實際上是一個單鏈表型的數據結構,鏈表中的前一個元素都會持有下一個元素的引用,而MessageQueue只需要持有第一個元素的引用即可。看一下MessageQueue的enqueueMessage方法:

    boolean enqueueMessage(Message msg, long when) {
            ...
            msg.when = when;
            Message p = mMessages;//當前鏈表中的首個元素
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {//如果鏈表中沒有元素或者要插入的message的執行時間早於隊列中的首個元素
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; //
                prev.next = msg;
            }
            ...
        }
        return true;
    }

當插入一個新的Message時,MessageQueue首先會判斷當前鏈表中是否存在元素,如果集合中的首個元素爲null,那麼就說明這個集合現在也是空的。如果集合中不存在元素,或新插入的Message不需要延時執行,或者要插入的Message的執行時間要早於集合中的首個元素的話,那麼直接將鏈表中的首個元素設置爲新插入的Message的下個元素,並將新插入的元素設置爲隊列中的首個元素。如果不滿足上述條件的話,那麼會從頭開始遍歷集合,根據Message的執行時間來將Message插入到集合中的相應位置。可見MessageQueue雖然名字中帶有Queue,但並不是一個標準的隊列,因爲隊列只允許在表的後端插入元素。

至此,一個Message就成功被Handler提交到了Message中了。

由於Android系統的消息機制比較複雜,本人技術水平非常有限,本文中難免會出現錯誤或者表達不準確的地方,希望大家能夠幫忙指出,謝謝!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章