Android Handler機制之迴圈訊息佇列的退出

AndyJennifer發表於2018-09-22

啦啦.jpeg

該文章屬於《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回收機制 》。關於非安全退出時,訊息佇列中的回收示意圖如下所示:

回收全部訊息.png

退出訊息迴圈的具體邏輯

上文中,我們描述了在非安全退出時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。

那麼到現在整個迴圈訊息佇列的退出的邏輯就清楚了。主要分為以下幾個步驟:

  1. Looper呼叫quitSafely()或quit()方法時會導致mQuitting標誌位為true。
  2. Looper呼叫quitSafely()或quit()方法時,內部會分別走MessageQueue的removeAllFutureMessagesLocked()與removeAllMessagesLocked(),上述兩種方法會回收訊息佇列中的訊息。
  3. Looper整個的訊息迴圈是通過其loop()方法。當MessageQueue中的next()獲取的訊息為空時,或導致整個迴圈訊息佇列的退出。
  4. 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()方法(也就是回收全部訊息),反之,則回收部分訊息,同時沒有被回收的訊息任然可以被取出執行。具體示意圖如下所示:

回收部分訊息.png

當使用安全退出迴圈訊息佇列時,整個退出邏輯與非安全退出有一定的區別。在上文中我們說過。當安全退出時,程式會判斷訊息佇列會根據訊息中的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比當前時間大,那麼就會被回收,反之仍然被取出執行。
  • 在整個迴圈訊息佇列退出的時候,如果在傳送訊息,那麼該訊息是會被會收的。

相關文章