該文章屬於《Android Handler機制之》系列文章,如果想了解更多,請點選 《Android Handler機制之總目錄》
前言
在上幾篇文中我們介紹了整個訊息的迴圈機制以及訊息的回收。現在我們來看看整麼退出迴圈訊息佇列。(到現在為止,整個Android Handler機制快要接近尾聲了。不知道大家看了整個系列的文章,有沒有對Handler機制有個深一點的瞭解。如果對你有所幫助,我也感到十分的開心與自豪~~~)。
訊息佇列的退出
要想結束迴圈訊息佇列,需要呼叫Loooper的quitSafely()或quit()方法,具體程式碼如下所示:
public void quitSafely() { mQueue.quit(true);}
public void quit() { mQueue.quit(false); }
複製程式碼
而該兩個方法的內部,都會呼叫Loooper中對應的MessageQueue的quit(boolean safe)方法。檢視quit(boolean safe)方法:
void quit(boolean safe) {
if (!mQuitAllowed) {//注意,主執行緒是不能退出訊息迴圈的
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {//如果當前迴圈訊息已經退出了,直接返回
return;
}
mQuitting = true;//該標記十分重要,十分重要
if (safe) {//如果是安全退出
removeAllFutureMessagesLocked();
} else {//如果不是安全退出
removeAllMessagesLocked();
}
nativeWake(mPtr);
}
}
複製程式碼
在MessageQueue的quit(boolean safe)方法中,會將mQuitting (用於判斷當前訊息佇列是否已經退出,該標誌位十分有用,下文會提到)置為true,同時會根據當前是否安全退出的標誌 (safe)來走不同的邏輯,如果安全則走removeAllFutureMessagesLocked()方法,如果不是安全退出則走removeAllMessagesLocked()方法。下面分別對這兩個方法進行討論。
非安全退出 removeAllMessagesLocked()方法
private void removeAllMessagesLocked() {
Message p = mMessages;//訊息佇列中的頭節點
while (p != null) {
Message n = p.next;
p.recycleUnchecked();//回收訊息
p = n;
}
mMessages = null;//將訊息佇列中的頭節點置為null
}
複製程式碼
非安全退出其實很簡單,就是遍歷訊息佇列中的訊息(訊息佇列內部結構是連結串列)將所有訊息佇列中的訊息全部回收。同時將MessageQueue中的mMessages (訊息佇列中的頭訊息)置為null,其中關於Message的recycleUnchecked()方法,如果你對該方法不是很熟悉,建議先閱讀《Android Handler機制之Message及Message回收機制 》。關於非安全退出時,訊息佇列中的回收示意圖如下所示:
退出訊息迴圈的具體邏輯
上文中,我們描述了在非安全退出時MessageQueue中僅僅進行了訊息的回收,而並沒有真正涉及到迴圈訊息佇列的退出,現在我們就來看一看息迴圈退出的具體邏輯。我們都知道整個訊息迴圈的訊息獲得,都是通過Loooper中的loop()方法,具體程式碼如下所示:
public static void loop() {
final Looper me = myLooper();
//省略部分程式碼...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
//如果沒有訊息,那麼說明當前訊息佇列已經退出
return;
}
//省略部分程式碼
}
}
複製程式碼
從程式碼中我們可以退出,整個loop()方法的結束,會與MessageQueue的next()方法相關,如果next()方法獲取的msg為null,那麼Looper中的loop方法也直接結束。那麼整個訊息迴圈也退出了。那接下來我們來檢視Message 中的next()方法,具體程式碼如下所示:
Message next() {
//省略部分程式碼...
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
synchronized (this) {
//省略部分程式碼..
if (mQuitting) {
dispose();
return null;
}
//省略部分程式碼..
}
}
複製程式碼
在Message中的next()方法中,如果mQuitting為true,該方法會直接就返回了。**大家還記得我們mQuitting是什麼時候置為true的嗎?**對!就是我們呼叫Loooper的quitSafely()或quit()方法(也就是呼叫MessageQueue.quit(boolean safe))時,會將mQuitting置為true。
那麼到現在整個迴圈訊息佇列的退出的邏輯就清楚了。主要分為以下幾個步驟:
- Looper呼叫quitSafely()或quit()方法時會導致mQuitting標誌位為true。
- Looper呼叫quitSafely()或quit()方法時,內部會分別走MessageQueue的removeAllFutureMessagesLocked()與removeAllMessagesLocked(),上述兩種方法會回收訊息佇列中的訊息。
- Looper整個的訊息迴圈是通過其loop()方法。當MessageQueue中的next()獲取的訊息為空時,或導致整個迴圈訊息佇列的退出。
- MessageQueue中的next()方法受mQuitting標誌位影響,當mQuitting=true時,next()方法會返回null。
安全退出removeAllFutureMessagesLocked()方法
現在為止,我們已經基本瞭解了整個迴圈訊息佇列退出的流程了。在瞭解了非安全退出的方法之後,我們再來看看安全退出時,涉及到的邏輯操作。上文我們已經提到過了,當Looper呼叫quitSafely()方法時,內部會走MessagQueue的removeAllFutureMessagesLocked()。具體程式碼如下:
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;//當前佇列中的頭訊息
if (p != null) {
if (p.when > now) {//判斷時間,如果Message的取出時間比當前時間要大直接移除
removeAllMessagesLocked();
} else {
Message n;
for (;;) {//繼續判斷,取佇列中所有大於當前時間的訊息
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {//將所有所有大於當前時間的訊息的訊息回收
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
複製程式碼
觀察上訴程式碼,在該方法中,會判斷當前訊息佇列中的頭訊息的時間是否大於當前時間,如果大於當前時間就會removeAllMessagesLocked()方法(也就是回收全部訊息),反之,則回收部分訊息,同時沒有被回收的訊息任然可以被取出執行。具體示意圖如下所示:
當使用安全退出迴圈訊息佇列時,整個退出邏輯與非安全退出有一定的區別。在上文中我們說過。當安全退出時,程式會判斷訊息佇列會根據訊息中的message.when來判斷是否回收訊息。那麼在訊息佇列中沒有被回收的訊息是仍然能被取出執行的。具體程式碼如下所示:
Message next() {
//省略部分程式碼...
for (;;) {
//省略部分程式碼...
synchronized (this) {
//省略部分程式碼...
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
//省略部分程式碼...
} else {
//省略部分程式碼...
//第一處。如果訊息佇列中仍然有訊息,是會取出並執行的。
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;
}
}
//省略部分程式碼...
// 第二處,退出迴圈訊息佇列
if (mQuitting) {
dispose();
return null;
}
//省略部分程式碼...
}
}
複製程式碼
從上述程式碼中我們可以明顯的看見,當我們的訊息佇列中仍然有訊息的時候,是會取出訊息並返回的。不管mQuitting的值是否設定為true。那麼當整個訊息佇列中的訊息取完以後。才會走返回null(圖上程式碼 第二處)。
當迴圈訊息佇列退出時,仍然傳送訊息
其實有很多小朋友們肯定會關注,"當我整個迴圈訊息佇列退出的時候,如果我仍然使用Handler傳送訊息,那麼我的訊息去那裡了呢,是被拋棄了,還是有什麼特殊處理呢?",下面我們就帶著這些疑惑來看看Handler機制中具體的處理。
我們都知道當呼叫Handler傳送訊息的時候,最終走的方法就是enqueueMessage()方法,其中該方法內部又會走MessageQueue的enqueueMessage(Message msg, long when)方法。具體程式碼如下所示:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
MessageQueue的enqueueMessage(Message msg, long when)方法,具體程式碼如下所示:
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) {
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;//返回該訊息沒有加入訊息佇列的標誌
}
//省略部分程式碼。。。
}
複製程式碼
通過觀察MessageQueue的enqueueMessage(Message msg, long when)方法,我們能得出該方法內部會判斷當前迴圈訊息隊裡是否退出,如果已經結束,那麼會將Handler傳送的訊息回收,同時會返回該訊息沒有加入訊息佇列的標誌(return false)。
總結
- 迴圈訊息佇列的退出是通過呼叫Loooper的quitSafely()或quit()方法,兩個退出方法都會導致訊息佇列中的訊息回收。
- quitSafely()與quit()方法的區別是,quit()會直接回收訊息佇列中的訊息,而quitSafely()會根據當前的時間進行判斷,如果訊息的meesage.when比當前時間大,那麼就會被回收,反之仍然被取出執行。
- 在整個迴圈訊息佇列退出的時候,如果在傳送訊息,那麼該訊息是會被會收的。