Android Handler機制之循環消息隊列的退出

啦啦.jpeg

該文章屬於《Android Handler機制之》系列文章,若是想了解更多,請點擊 《Android Handler機制之總目錄》安全

前言

在上幾篇文中咱們介紹了整個消息的循環機制以及消息的回收。如今咱們來看看整麼退出循環消息隊列。(到如今爲止,整個Android Handler機制快要接近尾聲了。不知道你們看了整個系列的文章,有沒有對Handler機制有個深一點的瞭解。若是對你有所幫助,我也感到十分的開心與自豪~~~)。bash

消息隊列的退出

要想結束循環消息隊列,須要調用Loooper的quitSafely()或quit()方法,具體代碼以下所示:oop

public void quitSafely() { mQueue.quit(true);}
public void quit() { mQueue.quit(false); }
複製代碼

而該兩個方法的內部,都會調用Loooper中對應的MessageQueue的quit(boolean safe)方法。查看quit(boolean safe)方法:post

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()方法。下面分別對這兩個方法進行討論。ui

非安全退出 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回收機制 》。關於非安全退出時,消息隊列中的回收示意圖以下所示:this

回收所有消息.png

退出消息循環的具體邏輯

上文中,咱們描述了在非安全退出時MessageQueue中僅僅進行了消息的回收,而並無真正涉及到循環消息隊列的退出,如今咱們就來看一看息循環退出的具體邏輯。咱們都知道整個消息循環的消息得到,都是經過Loooper中的loop()方法,具體代碼以下所示:spa

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。code

那麼到如今整個循環消息隊列的退出的邏輯就清楚了。主要分爲如下幾個步驟:cdn

  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比當前時間大,那麼就會被回收,反之仍然被取出執行。
  • 在整個循環消息隊列退出的時候,若是在發送消息,那麼該消息是會被會收的。
相關文章
相關標籤/搜索