Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Handler消息機制完全解析(二)MessageQueue的隊列管理

Handler消息機制完全解析(二)MessageQueue的隊列管理

編輯:關於Android編程

關於這個隊列先說明一點,該隊列的實現既非Collection的子類,亦非Map的子類,而是Message本身。因為Message本身就是鏈表節點。隊列中的Message mMessages;成員即為隊列,同時該字段直接指向隊列中下一個需要處理的消息。

添加到消息隊列enqueueMessage()

要將message添加到隊列除了提供message之外,還需提供消息觸發時間when
如果當前隊列為空則直接mMessage=message即可。否則就需要逐個對比隊列中每個message的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;
}
if (needWake) {
    nativeWake(mPtr);
}

先看下新消息需要放到隊頭的情況:p == null || when == 0 || when < p.when。即隊列為空,或者新消息需要立即處理,或者新消息處理的事件比隊頭消息更早被處理。這時只要讓新消息的next指向當前隊頭,讓mMessages指向新消息即可完成插入操作。
除了上述三種情況就需要遍歷隊列來確定新消息位置了,下面結合示意圖來說明。
假設當前消息隊列如下
初始狀態
開始遍歷:p向隊尾移,引入prev指向p上一個元素
prev
假設此時p所指消息的when比新消息晚,則新消息位置在prev與p中間
插入
最後便是調用native方法來喚醒(Linux的epoll,有興趣的自行百度)。

從隊列取出消息next()

這部分內容有點高能,請根據個人BPU(BrainProcessUnit)酌情理解。
首先這個方法需要返回Message,那麼我們現在來看看哪裡有return。(共三段,我們最後看第二段。)

第一段

final long ptr = mPtr;
if (ptr == 0) {
    return null;
}

如果mPtr為0則返回null。那麼mPtr是什麼?值為0又意味著什麼?在MessageQueue構造方法中調用了native方法並返回了mPtrmPtr = nativeInit();;在dispose()方法中將其值置0mPtr = 0;並且調用了nativeDestroy()。而dispose()方法又在finalize()中被調用。另外每次mPtr的使用都調用了native的方法,其本身又是long類型,因此推斷它對應的是C/C++的指針。因此可以確定,mPtr為一個內存地址,當其為0說明消息隊列被釋放了。這樣就很容易理解為什麼mPtr==0的時候返回null了。

第三段

你沒有看錯,第二段在後面

if (mQuitting) {
    dispose();
    return null;
}

這裡的意思也很明顯,當這個消息隊列退出的時候,返回空。而且在返回前調用了dispose()方法,顯然這意味著該消息隊列將被釋放。

第二段

這部分涉及到的代碼基本上就是這個next()方法本身了,但可以肯定的是這裡的返回語句是return msg;。同時從enqueueMessage()方法可以看出來,在這個隊列中取到的message對象不可能為空,因此這裡的返回絕對不為空。
如此一來就可以得出一個結論:如果next()方法為空說明這個消息隊列正在退出或將被釋放回收。
繼續來看這個next(),這個代碼有點長,所以先做個減法。
第一個要減的就是pendingIdleHandlerCount,這個局部變量初始為-1,後面被賦值mIdleHandlers.size();。這裡的mIdleHandlers初始為new ArrayList(),在addIdleHander()方法中增加元素,在removeIdleHander()方法中移除元素。而我們所用的Handeler並未實現IdleHandler接口,因此在next()方法中pendingIdleHandlerCount的值要麼為0,要麼為-1,因此可以看出與該變量相關的部分代碼運行情況是確定的,好的,把不影響循環控制的代碼減掉。
第二個要減的是Binder.flushPendingCommands()這個代碼看源碼說明:

Flush any Binder commands pending in the current thread to the kernel driver. This can be useful to call before performing an operation that may block for a long time, to ensure that any pending object references have been released in order to prevent the process from holding on to objects longer than it needs to.

這段話啥意不懂也沒關系,這裡只需要知道:Binder.flushPendingCommands()方法被調用說明後面的代碼可能會引起線程阻塞。然後把這段減掉。
第三個要減的是一個log語句if (DEBUG) Log.v(TAG, "Returning message: " + msg);
第四個要減的是上面提到的“第一段”返回null的語句,但是“第三段”得留著。
最後再把注釋干掉給上代碼:

Message next() {
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            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 {
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;
            }
            if (mQuitting) {
                dispose();
                return null;
            }
            if (pendingIdleHandlerCount <= 0) {//上面分析過該變量要麼為0要麼為-1
                mBlocked = true;
                continue;
            }
        }
        nextPollTimeoutMillis = 0;
    }
}

雖然還是很長,但也不能再減了。大致思路如下:先獲取第一個同步的message。如果它的when不晚與當前時間,就返回這個message;否則計算當前時間到它的when還有多久並保存到nextPollTimeMills中,然後調用nativePollOnce()來延時喚醒(Linux的epoll,有興趣的自行百度),喚醒之後再照上面那樣取message,如此循環。代碼中對鏈表的指針操作占了一定篇幅,其他的邏輯很清楚,就不一句句分析了。

從隊列移除消息removeMessages()

該方法有2個重載,除此之外還有removeCallbacksAndMessages()等方法也可以移除消息。但代碼段都基本一樣,這裡以void removeMessages(Handler h, int what, Object object){}方法為例。
該方法完整源碼如下

void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

最開始判斷handler是否為空不必多說,然後便是同步代碼段,只裡面有兩個while循環。為什麼有兩個呢?學過數據結構鏈表的都知道,鏈表分兩種:帶頭結點和不帶頭結點。而這兩種鏈表的遍歷方式有所不同:不帶頭結點的鏈表中,第一個元素需要單獨處理,然後才能將後續部分當做帶頭結點的鏈表來使用while循環遍歷。可以看出MessageQueue是不帶頭結點的鏈表,而且遍歷過程中有需要刪除節點,因此要特殊處理的不只是第一個元素,而是第一組符合刪除條件的元素。有點暈了是吧,不要緊,我們開始斗圖。

第一個while

假設需要遍歷的消息隊列如圖所示。
初始狀態
為了讓第一個while可以執行,我們假設前3個元素符合移除條件,即前三個Message的targewhatobj分別與指定的handlerwhatobject相同。首先第一個元素滿足條件進行如下操作:
執行n=p.next;
n=p.next
後移mMessage;
後移mMessage
回收p指向的元素,即第一個元素。
回收p指向的元素
讓p指向新的隊頭。
讓p指向新的隊頭
此時又與初始隊列狀態一樣了。先前我們假設隊頭有三個元素符合移除條件,因此再循環執行上面4圖2邊後又得到初始狀態的隊列,此時隊頭元素不滿足移除條件因此while終止,同時新的隊列變成了“帶頭結點的鏈表”,因此mMessage指向的元素永遠不用被判斷是否滿足移除條件。

第二個while

此時消息隊列狀態如下:
初始狀態
執行n=p.next;
n=p.next
假設n指向的元素不滿足移除條件,則只需要將p和n後移,如此也說明,p指向的元素總是已經被判斷過不滿足移除條件的。這部分邏輯很簡單到給圖就是看不起讀者的智商,現在我們假設n指向的元素滿足移除條件,即當前隊列如下:
滿足條件
執行nn=n.next;
nn=n.next
回收n指向的元素
回收n指向的元素
執行p.next=nn;
p.next=nn
這時p之後的隊列又是一個帶頭結點的鏈表。可以繼續while了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved