1. 程式人生 > >Handler機制之MessageQueue原始碼分析

Handler機制之MessageQueue原始碼分析

介紹:

一個用於儲存(被Looper分發的)Message列表的低階類。與Native world的MessageQueue由緊密聯絡

MessageQueue類內部實現了兩個Interface,一個靜態內部類。

  • 介面IdleHandler在訊息佇列沒有訊息時使用,處理poll狀態時的動作

  • 介面OnFileDescriptorEventListener在相應的檔案狀態改變(可讀,可寫,有錯誤)時被使用

  • 靜態內部類FileDescriptorRecord,記錄相應檔案狀態改變時的監視器OnFileDescriptorEventListener,在被native方法呼叫的dispatchEvents方法裡被呼叫,執行監視器

先來了解一下構造方法和屬性:

  //訊息佇列是否可以停止的標示,主執行緒是不可以停止,非同步執行緒該可以停止,該標示為true
  private final boolean mQuitAllowed;

  // native code(C++)層的引用 ,當為0,則停止了該訊息佇列
  private long mPtr; 

   //當前訊息佇列的頭資訊(即需要最先處理的資訊)
    Message mMessages;
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
    private IdleHandler[] mPendingIdleHandlers;
    private boolean mQuitting;

    //用於標示next()方法是否阻塞,等待pollOne()非零時間
    private boolean mBlocked;
    
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        //呼叫native層建立相應的C++類物件,返回引用
        mPtr = nativeInit();
    }

接下來,瞭解Handler.sendMessage()傳入Message,最終呼叫到enqueueMessage()

    boolean enqueueMessage(Message msg, long when) {
        //message的target為空,即Handler為空,會跑出異常
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //Message若是在使用期間,會被跑出異常
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            //訊息佇列已經停止,則停止新加入message
            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;
            }
            //標記該message在使用狀態
            msg.markInUse();
            msg.when = when;
            //獲取到上一個Message
            Message p = mMessages;
            //是否喚醒的標示
            boolean needWake;
            //訊息佇列中沒有資訊,或者需要立即執行的message,或者當前訊息的處理時間小於訊息佇列中頭訊息的處理時間,則將當前資訊作為訊息佇列中的頭訊息。
            //與此同時,若是訊息佇列處於阻塞狀態,則需要喚醒
            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迴圈方式,將當前傳入的Message插入到訊息佇列中的合適位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    //找到比當前傳入的訊息的處理時間大的Message
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //將傳入的message插入到連結串列中合適位置
                msg.next = p; 
                prev.next = msg;
            }
            //若是需要喚醒,則呼叫Native層的喚醒,不在阻塞狀態
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

總結:一個訊息插入到訊息佇列中,若是插入到合適的中間位置,則不需要喚醒。若是插入到新的頭部,與此同時,訊息佇列處於阻塞狀態,則需要喚醒。

接下來,檢視一下Looper.looper()方法中呼叫到的next():

 Message next() {
        //Native層的引用已經斷開,訊息佇列已經停止,則返回null。
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //用於標示第一次遍歷,標示為-1
        int pendingIdleHandlerCount = -1; 
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                //執行緒先前處於睡眠狀態,重新重新整理那些等待處理的Binder程序間通訊請求,避免它們長時間得不到處理。
                Binder.flushPendingCommands();
            }
            //檢查當前執行緒的訊息佇列中是否有新訊息需要處理,若是nextPollTimeoutMillis為-1,則該執行緒會進入睡眠等待狀態      
            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) {
                    // 迴圈方式,獲取到下一個非同步資訊,非同步訊息不需要被同步阻塞
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        //當前時間小於訊息的執行時間,則計算出需要等待時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //阻塞標記為false
                        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 {
                    // 沒有更多資訊,執行緒會進入睡眠等待狀態,直到被其他執行緒喚醒。
                    nextPollTimeoutMillis = -1;
                }
                // 訊息佇列已經停止,呼叫Native層銷燬方法
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //獲取到空閒訊息的監聽器個數,當第一次遍歷,且訊息佇列中沒有訊息或者處於阻塞狀態,則進行空閒資訊的回撥操作
                if (pendingIdleHandlerCount <0&& (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                //若是沒有空閒訊息的監聽器或者本次next()方法已經發送過一次空閒訊息,則不走以下邏輯操作
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null;
                boolean keep = false;
                try {
                    //回撥空閒訊息的監聽器的queueIdle()
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {//不在保持監聽狀態,則移除掉
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            //重置狀態,每次Next()最多發出一個空閒資訊
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

總結:MessageQueue的next()一次呼叫,最多隻會發送一個空閒訊息,觸發空閒訊息的監聽器的queueIdle()

空閒訊息的監聽器IdleHandler的新增與移除方法:

    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
    public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.remove(handler);
        }
    }