Android Handler機制6之MessageQueue簡介

Android Handler機制系列文章整體內容如下:

本片文章的主要內容如下:

  • 1、MessageQueue簡介
  • 2、MessageQueue類注釋
  • 3、MessageQueue成員變量
  • 4、MessageQueue的構造函數
  • 5、native層代碼的初始化
  • 6、IdleHandler簡介
  • 7、MessageQueue中的Message分類

MessageQueue官網

一、MessageQueue簡介

MessageQueue即消息隊列,這個消息隊列和上篇文章里面的Android Handler機制5之Message簡介與消息對象對象池里面的 消息對象池不是同一個東西。MessageQueue是一個消息隊列,Handler將Message發送到消息隊列中,消息隊列會按照一定的規則取出要執行的Message。需要注意的是Java層的MessageQueue負責處理Java的消息,native也有一個MessageQueue負責處理native的消息,本文重點是Java層,所以暫時不分析native源碼。

二、MessageQueue類注釋

MessageQueue.java源碼地址

Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.
You can retrieve the MessageQueue for the current thread with Looper.myQueue().

翻譯一下:

它是一個被Looper分發、低等級的持有Message集合的類。Message并不是直接加到MessageQueue的,而是通過Handler對象和Looper關聯到一起。
我們可以通過Looper.myQueue()方法來檢索當前線程的MessageQueue
它是一個低等級的持有Messages集合的類,被Looper分發。Messages并不是直接加到MessageQueue的,而是通過Handler對象和Looper關聯到一起。我們可以通過Looper.myQueue()方法來檢索當前線程的

三、MessageQueue成員變量

    // True if the message queue can be quit.
    //用于標示消息隊列是否可以被關閉,主線程的消息隊列不可關閉
    private final boolean mQuitAllowed;

    @SuppressWarnings("unused")
    // 該變量用于保存native代碼中的MessageQueue的指針
    private long mPtr; // used by native code

    //在MessageQueue中,所有的Message是以鏈表的形式組織在一起的,該變量保存了鏈表的第一個元素,也可以說它就是鏈表的本身
    Message mMessages;

    //當Handler線程處于空閑狀態的時候(MessageQueue沒有其他Message時),可以利用它來處理一些事物,該變量就是用于保存這些空閑時候要處理的事務
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

    // 注冊FileDescriptor以及感興趣的Events,例如文件輸入、輸出和錯誤,設置回調函數,最后
    // 調用nativeSetFileDescriptorEvent注冊到C++層中,
    // 當產生相應事件時,由C++層調用Java的DispathEvents,激活相應的回調函數
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;

     // 用于保存將要被執行的IdleHandler
    private IdleHandler[] mPendingIdleHandlers;

    //標示MessageQueue是否正在關閉。
    private boolean mQuitting;

    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
    // 標示 MessageQueue是否阻塞
    private boolean mBlocked;

    // The next barrier token.
    // Barriers are indicated by messages with a null target whose arg1 field carries the token.
    // 在MessageQueue里面有一個概念叫做障柵,它用于攔截同步的Message,阻止這些消息被執行,
    // 只有異步Message才會放行。障柵本身也是一個Message,只是它的target為null并且arg1用于區分不同的障柵,
     // 所以該變量就是用于不斷累加生成不同的障柵。
    private int mNextBarrierToken;

四、MessageQueue的構造函數

通過分析下圖


MessageQueue構造函數.png

我們知道MessageQueue就一個構造函數
代碼在MessageQueue.java 68行

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

MessageQueue只是有一個構造函數,該構造函數是包內可見的,其內部就兩行代碼,分別是設置了MessageQueue是否可以退出和native層代碼的相關初始化

五、native層代碼的初始化

在MessageQueue的構造函數里面調用 nativeInit(),我們來看下
代碼在MessageQueue.java 61行

    private native static long nativeInit();

根據Android跨進程通信IPC之3——關于"JNI"的那些事中知道,nativeInit這個native方法對應的是android_os_MessageQueue.cpp里面的android_os_MessageQueue_nativeInit(JNIEnv* , jclass )函數

代碼在android_os_MessageQueue.cpp 172 行

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    // 初始化native消息隊列
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

后面在講解流程時候的會詳細講解,這里就不如深入了。

六、IdleHandler簡介

作為Android開發者我們知道,Handler除了用于發送Message,其本身也承載著執行具體業務邏輯的責任handlerMessage(Message msg),而IdleHandler在處理業務邏輯方面和Handler一樣,不過它只會在線程空閑的時候才執行業務邏輯的處理,這些業務經常是哪些不是很緊要或者不可預期的,比如GC。

(一) IdleHandler接口

代碼在MessageQueue.java 777行

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

老規矩 先來翻譯一下接口的注釋:

回調的接口,當線程空閑的時候可以利用它來處理一些業務員

這個IdleHandler接口就一個抽象方法queueIdle,我也看一下抽象方法的注釋

當消息隊內所有的Message都執行完之后,該方法會被調用。該返回值為True的時候,IdleHandler會一直保持在消息隊列中,False則會執行完該方法后移除IdleHandler。需要注意的是,當消息隊列中還有其他Delay Message并且這些Message還沒到被執行的時間的時候,由于線程是空閑的,所以IdleHandler也可能會被執行,

從源碼可以看出IdleHandler其實就是一個簡單的回調接口,內部就一個帶返回值的方法boolean queueIdle(),在使用的時候只需要實現該接口并加入到MessageQueue中就可以了,例如

從源碼可以看出IdleHandler其實就是一個簡單的回調接口,內部就一個帶返回值的方法boolean queueIdle(),在使用的時候只需要實現該接口并加入到MessageQueue中就可以了,例如下面簡答的代碼所示

    MessageQueue messageQueue = Looper.myQueue();
    messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            // do something.
            return false;
        }
    });

(二) 添加IdleHandler:addIdleHandler(IdleHandler handler)

代碼在MessageQueue.java 115行

    /**
     * Add a new {@link IdleHandler} to this message queue.  This may be
     * removed automatically for you by returning false from
     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
     *
     * <p>This method is safe to call from any thread.
     *
     * @param handler The IdleHandler to be added.
     */
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

看先注釋:

  • 添加一個新的IdleHanlder到消息隊列中,當IdleHandler的回調方法返回False的時候,該IdleHanlder在被執行后會被立即移除,你也可以通過調用removeIdleHandler(IdleHandler handler)方法來移除指定的IdleHandler。
  • 在任何線程中調用該方法都是安全的。

方法內部很簡單,就是三步

  • 第一步,做非空判斷
  • 第二步,加一個同步鎖
  • 第三步,調用mIdleHandlers.add(handler);添加 (PS:mIdleHandlers是一個ArrayList)

(四) 刪除IdleHandler:removeIdleHandler(IdleHandler handler)

代碼在MessageQueue.java
133行

    /**
     * Remove an {@link IdleHandler} from the queue that was previously added
     * with {@link #addIdleHandler}.  If the given object is not currently
     * in the idle list, nothing is done.
     *
     * <p>This method is safe to call from any thread.
     *
     * @param handler The IdleHandler to be removed.
     */
    public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.remove(handler);
        }
    }

看先注釋:

  • 從消息隊列中移除一個之前添加的IdleHandler。如果該IdleHandler不存在,則什么也不做。

移除IdleHandler的方法同樣很簡單,下一步同步處理然后直接mIdleHandlers.reomve(handler)就可以了。

七、MessageQueue中的Message分類

在MessageQueue中,Message被分成3類,分別是

  • 同步消息
  • 異步消息
  • 障柵

那我們就一次來看下:

(一) 同步消息:

正常情況下我們通過Handler發送的Message都屬于同步消息,除非我們在發送的時候執行該消息是一個異步消息。同步消息會按順序排列在隊列中,除非指定Message的執行時間,否咋Message會按順序執行。

(二) 異步消息:

想要往消息隊列中發送異步消息,我們必須在初始化Handler的時候通過構造函數public Handler(boolean async)中指定Handler是異步的,這樣Handler在講Message加入消息隊列的時候就會將Message設置為異步的。

(三) 障柵(Barrier):

障柵(Barrier) 是一種特殊的Message,它的target為null(只有障柵的target可以為null,如果我們自己視圖設置Message的target為null的話會報異常),并且arg1屬性被用作障柵的標識符來區別不同的障柵。障柵的作用是用于攔截隊列中同步消息,放行異步消息。就好像交警一樣,在道路擁擠的時候會決定哪些車輛可以先通過,這些可以通過的車輛就是異步消息。

同步和異步.png
1、添加障柵:postSyncBarrier()

代碼在MessageQueue.java
458行

    /**
     * Posts a synchronization barrier to the Looper's message queue.
     *
     * Message processing occurs as usual until the message queue encounters the
     * synchronization barrier that has been posted.  When the barrier is encountered,
     * later synchronous messages in the queue are stalled (prevented from being executed)
     * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
     * the token that identifies the synchronization barrier.
     *
     * This method is used to immediately postpone execution of all subsequently posted
     * synchronous messages until a condition is met that releases the barrier.
     * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
     * and continue to be processed as usual.
     *
     * This call must be always matched by a call to {@link #removeSyncBarrier} with
     * the same token to ensure that the message queue resumes normal operation.
     * Otherwise the application will probably hang!
     *
     * @return A token that uniquely identifies the barrier.  This token must be
     * passed to {@link #removeSyncBarrier} to release the barrier.
     *
     * @hide
     */
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

看先注釋:

  • 向Looper的消息隊列中發送一個同步的障柵(barrier)
  • 如果沒有發送同步的障柵(barrier),消息處理像往常一樣該怎么處理就怎么處理。當發現遇到障柵(barrier)后,隊列中后續的同步消息會被阻塞,直到通過調用removeSyncBarrier()釋放指定的障柵(barrier)。
  • 這個方法會導致立即推遲所有后續發布的同步消息,知道滿足釋放指定的障柵(barrier)。而異步消息則不受障柵(barrier)的影響,并按照之前的流程繼續處理。
  • 必須使用相同的token去調用removeSyncBarrier(),來保證插入的障柵(barrier)和移除的是一個同一個,這樣可以確保消息隊列可以正常運行,否則應用程序可能會掛起。
  • 返回值是障柵(barrier)的唯一標識符,持有個token去調用removeSyncBarrier()方法才能達到真正的釋放障柵(barrier)

方法內部很簡單就是調用了postSyncBarrier(SystemClock.uptimeMillis()),通過Android Handler機制3之SystemClock類,我們知道SystemClock.uptimeMillis()是手機開機到現在的時間。那我們來看下這個postSyncBarrier(long)方法

1.1、postSyncBarrier(long when)
在講解之前先補充一個知識點:MessageQueue里面的所有Message是按照時間從前往后有序排列的。后面我們講解Handler機制流程的時候會詳細說明

代碼在MessageQueue.java
462行

 private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
              /** 第一步 */
            final int token = mNextBarrierToken++;
             /** 第二步 */
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

              /** 第三步 */
            Message prev = null;
            //把消息隊列的第一個元素指向p
            Message p = mMessages;
            if (when != 0) {
             /** 第四步 */
                while (p != null && p.when <= when) {
                    //通過p的時間點和障柵的時間點的比較,如果比障柵的小,就把消息隊列中的消息向后移動一位(因為消息隊列中所有元素是按照時間排序的)
                    prev = p;
                    p = p.next;
                }
            }
              /** 第五步 */
             //prev != null 代表不是消息隊列的頭部,則需要考慮前面一個消息和后面的一個消息
            if (prev != null) { // invariant: p == prev.next
                //msg的下一個消息是p 
                msg.next = p;
                 //msg的上一個消息是msg
                prev.next = msg;
            } else {
                //prev == null 代表是消息隊列的頭部,則只需要負責下一個消息即可
                msg.next = p;
                //設置自己是消息隊列的頭部
                mMessages = msg;
            }
            /** 第六步 */
            return token;
        }
    }

方法詳解

  • 第一步 獲取障柵的唯一標示,然后自增該變量作為下一個障柵的標示,通過mNextBarrierToken ++,我們得知,這些唯一標示是從0開始,自加1的。
  • 第二步 從Message消息對象池中獲取一個Message,并重置它的when和arg1。并且arg1為token的值,通過msg.markInUse()標示msg正在被使用。這里并沒有給tareget賦值。
  • 第三步 創建變量pre和p為第四步做準備,其中p被賦值為mMessages,而mMessages未消息隊列的第一個元素,所以p此時就是消息隊列的第一個元素。
  • 第四步 通過對 隊列中的第一個Message的when和障柵的when作比較,決定障柵在整個消息隊列中的位置,比如是放在隊列的頭部,還是隊列中第二個位置,如果障柵在頭部,則攔截后面所有的同步消息,如果在第二的位置,則會放過第一個,然后攔截剩下的消息,以此類推。
  • 第五步 把msg插入到消息隊列中
  • 第六步 返回token

從源碼中我們可以看出,在把障柵插入隊列的時候先通過when的比較,根據不同的情況把障柵插入到不同的位置,具體情況如下圖所示:
ps:藍色的為Message、紅色的為Barrier

當Message.when<Barrier.when,也就是第一個Message的執行時間點在障柵之前。


障柵插入隊列1.png

當Message.when>=Barrier.when,也就是第一個Message的執行時間點在障柵之后。


障柵插入隊列2.png
大家在看上面的代碼的時候有沒有注意到一個事情,就是msg這個對象的target是null,因為從始至終就沒有賦值過,這也是后面在移除障柵的時候通過判斷條件之一:是target是否為null來判斷的
2、移除障柵

代碼在MessageQueue.java
501行

   /**
     * Removes a synchronization barrier.
     *
     * @param token The synchronization barrier token that was returned by
     * {@link #postSyncBarrier}.
     *
     * @throws IllegalStateException if the barrier was not found.
     *
     * @hide
     */
    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            // 獲取消息隊列的第一個元素
            Message p = mMessages;
            //遍歷消息隊列的所有元素,直到p.targe==null并且 p.arg1==token才是我們想要的障柵
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            //是否需要喚醒
            final boolean needWake;
            //如果是障柵是不是第一個圓度
            if (prev != null) {
                //跳過障柵,將障柵的上一個元素的next指向障柵的next
                prev.next = p.next;
                //因為有元素,所以不需要喚醒
                needWake = false;
            } else {
                //如果是第一個元素,則直接下消息隊列中的第一個元素指向障柵的下一個即可
                mMessages = p.next;
                 //如果消息隊列中的第一個元素是null則說明消息隊列中消息,所以需要喚醒
                 //
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

刪除障柵(barrier)的方法也很簡單,就是不斷地遍歷消息隊列(鏈表結構),直到倒找與指定的token相匹配的障柵,然后把它從隊列中移除。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容