Handler是如何發送消息到 MessageQueue 的?MessageQueue又是啥?

Handler是如何發送消息到 MessageQueue 的?MessageQueue又是啥?

最近一直複習Android的知識點,這兩週也一直在看handler的源碼,爲了加深本身的理解的深度遂決定用博客的形式把它記錄下來,handler 你們都用過,這裏不對它的用法進行分析,你們都知道 handler 是用於在 Android 中進行線程間消息切換的,但它是如何作到的呢?爲何在子線程中直接建立handler對象會crash呢?Android系統的開發設計人員爲何不容許在子線程中更新UI呢,又是出於什麼考慮的?帶着這些疑問我從handler發送消息的那塊一步步的分析。安全

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        handler = new Handler();
        Message message = Message.obtain();
        message.obj = "從子線程中建立的消息";
        handler.sendMessage(message);
        Looper.loop();
    }
}).start();
複製代碼

先不要看Looper的部分,這個小節裏主要就看 handler.sendMessage(message); 這行代碼它到底作了什麼,點開代碼跟進去看下。bash

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) {
    MessageQueue queue = mQueue;
    if (queue == null) {
         //省略。。。
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    //省略。。。
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼

上面一共就摘錄了4個函數,從源碼中能夠得知handler發送消息其實就是按照 sendMessage 、sendMessageDelayed、sendMessageAtTime這樣的順序來的,而且最後調用了 MessageQueue 的 enqueueMessage 方法執行消息的插入,在 sendMessageAtTime 方法中咱們第一次看到了 MessageQueue這個概念,它被mQueue賦值,mQueue是經過 mLooper.mQueue; 獲取的,也就是說Looper對象中維護了一個MessageQueue,這個在下文中會講到,接下來看下消息是如何被插入到消息隊列中的,我把 MessageQueue 的 enqueueMessage 方法複製到下面。多線程

boolean enqueueMessage(Message msg, long when) {
    //若是 Message 的 target 屬性爲null則拋出 IllegalArgumentException 異常,這個target其實就是handler,在handler類中
    //的enqueueMessage方法中爲其賦值 msg.target = this; 每個消息 Message 對象都維護了一個 handler 對象
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //若是該消息已經被標記爲使用了那麼將拋異常,同一個消息不能被髮送兩次
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        //將消息標記爲使用
        msg.markInUse();
        //給message的when變量賦值,when是發送消息時用戶本身定義的消息延遲
        msg.when = when;
        //第一次發送消息時 mMessages 值爲null,因此當第一次發送消息時 p = null
        Message p = mMessages;
        boolean needWake;
        //由於在第一次發送消息時 p == null,因此第一次會執行到if判斷裏,這裏假設第一次發送消息時when的值爲 SystemClock.uptimeMillis()+0
        if (p == null || when == 0 || when < p.when) {
            //MessageQueue是用單鏈表實現的隊列,這裏將當前message結點的後繼結點next指向null
            msg.next = p;
            //爲 mMessages 賦值
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //假設當你第二次發送消息而且when的值 > Message01.when 時,因爲第一次發送消息時 mMessages 被賦值爲 第一次發送的消息對象(這裏我用Message01指代),
            //當你第二次發送消息Message02(when > Message01.when),因爲 p = Message01,因此 when > p.when,條件不成立執行到 else 代碼塊中
            //定義  Message prev,此時prev=null
            Message prev;
            //開啓for循環將消息按when參數的值做爲條件進行排序
            for (;;) {
                //prev = message01
                prev = p;
                //p = null
                p = p.next;
                //條件容許跳出循環
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            //message02.next = null
            msg.next = p; 
            //message01.next = message02
            //我用message01和message02舉例子來分析handler發送消息到底執行了什麼,上面註釋可能會理解起來有點凌亂,沒事我下面會用圖片的形式在描述下,
            //其實你能夠發現MessageQueue這個消息隊列是用單鏈表的形式來體現的,enqueueMessage的執行邏輯就是將消息按照when參數進行排序(when從小到大),
            //所謂when其實就是發送消息時定義的消息延遲
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
複製代碼

接下來我會用handler發送4條消息,咱們來看下這4條消息時如何排序的,我會用圖文的方式跟着代碼一步步的走併發

handler.sendMessage(message01);//步驟 1 when = 0
handler.sendMessageDelayed(message03,2000L);//步驟 2 when實際 = SystemClock.uptimeMillis()+2000L,這裏方便就將when理解爲2000,下面也是同樣
handler.sendMessageDelayed(message04,500L);//步驟 3
複製代碼

看到這個圖片可能會犯迷糊,由於我也不知道該用文原本形容這個排序的過程,不過handler發送消息的過程其實就是將消息塞到 MessageQueue 中,而 MessageQueue 是用單鏈表的形式來體現的而且按參數 when 來進行從小到大的排序,到時候取數據的時候也是從這個鏈表中獲取,固然是從鏈表頭開始獲取數據,獲取數據的時候會依據當前時間與這個消息對象所維護的when進行對比,若是當前時間小於要發送的這個when,那麼就會處於「休眠」狀態,這歌休眠其實挺有意思的,咱們在接下來小結中會繼續講解。

題目解答

爲何在子線程中直接建立handler對象會crash呢

文章開頭幾個小題我開的有點大,由於我發現其實和本文沒什麼關係,第一題須要把handler後面的文章所有寫完才能回答,因此咱們看下這題,首先觀察下handler的構造方法async

public Handler(Callback callback, boolean async) {
        //省略部分代碼。。。
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //省略部分代碼。。。
    }
複製代碼

這裏有個 Looper.myLooper(); 它的做用是從 ThreadLocal 中獲取當前線程的 Looper 對象的,因爲在子線程中沒有執行初始化Looper並將其保存到 ThreadLocal 中,因此直接獲取會報RuntimeException,這也是爲何子線程中須要在 handler 初始化以前執行 Looper.prepare(); 了,您能夠點開這代碼本身看下源碼,這裏簡單的講下,在下一小結講Looper中我會按照源碼具體講解。ide

Android系統的開發設計人員爲何不容許在子線程中更新UI呢,又是出於什麼考慮的?

若是容許在子線程中更新UI是否是會遇到多線程併發的問題?若是一個控件在不一樣的線程中均可以更新UI那麼如何保證它的線程安全呢?假設出於這樣的考慮就必須加上鎖機制,鎖機制那是很是耗費資源的,同時還加大了編碼的複雜度,因此還不如只容許在主線程中更新UI呢。函數

相關文章
相關標籤/搜索