Handler機制之MessageQueue原始碼分析
阿新 • • 發佈:2019-01-31
介紹:
一個用於儲存(被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);
}
}