1. 程式人生 > >MessageQueue&Message Pool—訊息的讀寫、刪除和回收

MessageQueue&Message Pool—訊息的讀寫、刪除和回收

如有原始碼,版本為android6.0

訊息的讀寫和刪除對應的類是MessageQueue,這裡的寫也伴隨著刪除,回收有個抽象的概念Global Pool或者Message Pool,前者obtain()方法註釋有提到,後者我感覺更形象。當然回收是在Looper的loop()方法中。

1、MessageQueue的資料結構

翻譯為訊息佇列,但實際的資料結構是單向連結串列結構,下文中仍以訊息佇列稱之;

Message中有個重要的屬性Message next;next指向另外一個Message。在MessageQueue中Message A的next指向B,B的next指向C;這樣 A B C就組成了單向連結串列結構,它們是以時間排序的,其中A為header,表示最先要處理的訊息,C是最後要處理的訊息,這個header是MessageQueue的一個屬性,這樣有了這個Header就擁有整個訊息連結串列。


2、MessageQueue三種操作

插入、獲取、刪除;對應方法為enqueueMessage()、next()、removeMessage()

2.1插入訊息

Handler的send()系列方法最終呼叫的就是enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
        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) {//同步鎖,插入、刪除和獲取都是用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;//隊的條件就這個when,意思距離處理的時間,數值越大排隊越靠後。
            Message p = mMessages;//mMessages就是佇列的頭
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {//p==null說明佇列為空,when==0意思是現在就要處理,沒有任何延遲,when<p.when,說明比
//現在的header還要早處理。
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;//換新header,新的header的next指向原來的header。
                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.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {//p==null說明到了佇列的尾部
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next 
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);//喚醒執行緒
            }
        }
        return true;
    }
這個方法的簡單描述就是:按照時間順序,將訊息插入訊息佇列中,有可能是頭,有可能是中間,也有可能是尾部。


2.2獲取訊息

獲取訊息是在Looper的loop()迴圈中
Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {//容錯? target不會為null
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
   這個方法意思是說:取連結串列的header,如果這個訊息時間不滿足,執行緒就阻塞,等時間到了就取出這個訊息,然後第二個訊息作為header。也有可能新插入了一個訊息喚醒了執行緒,繼續迴圈判斷,直到取出一個訊息為止。

2.3刪除訊息

removeMessages(Handler h, int what, Object object) 和removeMessages(Handler h, Runnable r, Object object)   這裡不做過多的解讀了;簡單概括還是對訊息佇列的遍歷,找到符合條件的Message,刪除這個Message,刪除的Message是要被回收的,回收的訊息放入訊息池中。

3、訊息池

訊息池沒有對應的類,在SDK中以global pool稱呼。訊息池中的訊息資料結構是

   private static final Object sPoolSync = new Object();// 訊息池操作,同步鎖類 
    private static Message sPool;// 訊息池的棧頂
    private static int sPoolSize = 0;//訊息池的訊息個數 
    private static final int MAX_POOL_SIZE = 50;//訊息池的訊息個數上限

上面是Message中屬性。

3.1提供訊息—出棧

  /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {//提供和回收 用的是同一個同步鎖
            if (sPool != null) {//訊息池中 棧頂沒資料則訊息池為空
                Message m = sPool;
                sPool = m.next;//第一個訊息出棧,第二個訊息作為棧頂
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;//訊息池數量減少一個
                return m;//
            }
        }
        return new Message();//訊息池沒有訊息 則new一個。
    }

3.2 訊息回收—壓棧

 /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
//清空訊息
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {//執行緒同步
            if (sPoolSize < MAX_POOL_SIZE) {//棧中訊息數量是有限制的
                next = sPool;//壓棧,原來棧頂的作為本訊息的next,即第二個訊息
                sPool = this;
                sPoolSize++;
            }
        }
    }

從上面兩個操作可以看出,MessageQueue中的訊息和MessagePool中的Message之間的連線的方式是一樣的,都是通過next指向下一個Message,但MessageQueue的資料結構是連結串列,但MessagePool是棧。