詳解Handler中消息隊列的入隊邏輯

一、源碼分析

具體分析請見代碼註釋:java

/** * 消息隊列是以執行時間爲序的優先級隊列 * * @param msg * @param when * @return */
boolean enqueueMessage(Message msg, long when) {

    //入隊消息沒有綁定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) {
        //入隊消息正在被取消發送
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            //回收入隊消息
            msg.recycle();
            return false;
        }
        //標記入隊消息爲正在使用中
        msg.markInUse();
        //入隊消息的執行時間
        msg.when = when;
        //獲取消息隊列的隊首消息
        Message p = mMessages;
        //是否須要喚醒線程
        boolean needWake;
        //若是隊列首部爲null,也就是隊列爲空
        //或若是入隊消息的執行時間爲0,也就是入隊消息須要立刻執行
        //或若是入隊消息的執行時間小於,也就是早於隊首消息的執行時間
        if (p == null || when == 0 || when < p.when) {
            // 新頭,喚醒事件隊列若是阻塞。
            // New head, wake up the event queue if blocked.
            // 入隊消息的指針指向隊首消息
            msg.next = p;
            // 入隊消息成爲新的隊首消息
            mMessages = msg;
            // 且若是線程已經阻塞
            // 則須要喚醒
            needWake = mBlocked;
        } else {
            // 插入到隊列中間。一般咱們不須要喚醒事件隊列,除非在隊列的頂部有一個屏障,而且消息是隊列中最先的異步消息。
            // Inserted within the middle of the queue. Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.

            // 若是線程已經阻塞
            // 且若是隊列的隊首消息的Handler爲空,也就是隊首消息是同步屏障消息
            // 且若是入隊消息是異步的,也就是能夠經過同步屏障
            // 則須要喚醒
            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; // invariant: p == prev.next
            // 將前一條消息的指針指向入隊消息
            prev.next = msg;
        }

        // 咱們能夠假設mPtr != 0,由於m是假的。
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            //若是須要喚醒,則進行喚醒
            nativeWake(mPtr);
        }
    }
    return true;
}
複製代碼

二、消息是分哪些狀況入隊的?如何入隊?

咱們剖除入隊規則、同步鎖、同步屏障消息、異步消息、喚醒規則等邏輯,將入隊的邏輯代碼抽出,獲得:markdown

public class Message {
    public Object obj;
    public long when;
    public Message next;
}
複製代碼
public class MessageQueue {
    public Message mMessages;

    public void enqueueMessage(Message msg, long when) {
        msg.when = when;
        Message p = mMessages;
        if (p == null || when == 0 || when < p.when) {
            //往空隊列和隊列頭插入消息
            msg.next = p;
            mMessages = msg;
        } else {
            Message prev;
            for (; ; ) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    //往隊列尾和隊列中插入消息
                    break;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
    }
}
複製代碼

2.一、往空隊列插入消息

2.二、在隊列頭插入消息

2.三、在隊列尾插入消息

2.四、在隊列中插入消息

三、消息入隊時,什麼狀況下須要主動喚醒線程?

3.一、隊列中沒有任何消息,且線程阻塞

此時新消息入隊後便主動喚醒線程,不管新消息是同步消息、異步消息。less

3.二、隊首的消息執行時間未到,且線程阻塞

若是在阻塞時長未耗盡時,就新加入早於隊首消息處理時間的消息,須要主動喚醒線程。 一、若是入隊消息的執行時間爲0,也就是入隊消息須要立刻執行。 二、若是入隊消息的執行時間小於隊首消息的執行時間,也就是入隊消息要早於隊首消息執行。異步

3.三、隊首消息是同步屏障消息,而且隊列中不含有異步消息,且線程阻塞

若是新加入的消息仍然是晚於隊首同步障礙器處理時間,那麼此次新消息的發佈在next()層面上是毫無心義的,咱們也不須要喚醒線程。 只有在新加入早於隊首同步障礙器處理時間的同步消息時,或者,新加入異步消息時(不論處理時間),纔會主動喚醒被next()阻塞的線程。async

3.四、隊首消息是同步屏障消息,隊列中含有異步消息但執行時間未到,切線程阻塞

由於隊首同步障礙器的緣故,不管新加入什麼同步消息都不會主動喚醒線程。 即便加入的是異步消息也須要其處理時間早於設定好喚醒時執行的異步消息,纔會主動喚醒。源碼分析

歡迎關注Android技術堆棧,專一於Android技術學習的公衆號,致力於提升Android開發者們的專業技能! 學習

Android技術堆棧
相關文章
相關標籤/搜索