Android Handler機制之Message的傳送與取出

AndyJennifer發表於2018-09-22

該文章屬於《Android Handler機制之》系列文章,如果想了解更多,請點選 《Android Handler機制之總目錄》

前言

在前面的文章中,我們已經大概瞭解了ThreadLocal的內部原理,以及Handler發訊息的大概流程。如果小夥伴如果對Handler機制不熟,建議閱讀《Android Handler機制之ThreadLocal》《Android Handler機制之Handler 、MessageQueue 、Looper》。該篇文章主要著重講解Message的傳送與取出的具體邏輯細節。在此之前,我們先回顧一下Handler傳送訊息的具體流程。

HandlerLooperMessage關係.png

訊息的傳送

我們都知道當呼叫Handler傳送訊息的時候,不管是呼叫sendMessage,sendEmptyMessage,sendMessageDelayed還是其他傳送一系列方法。最終都會呼叫sendMessageDelayed(Message msg, long delayMillis)方法。

  public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
複製程式碼

該方法會呼叫sendMessageAtTime()方法。其中第二個引數是執行訊息的時間,是通過從開機到現在的毫秒數(手機睡眠的時間不包括在內)+ 延遲執行的時間。這裡不使用System.currentTimeMillis() ,是因為該時間是可以修改的。會導致延遲訊息等待時間不準確。該方法內部會呼叫sendMessageAtTime()方法,我們接著往下走。

   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
複製程式碼

這裡獲取到執行緒中的MessageQueue物件mQueue(在建構函式通過Looper獲得的),並呼叫enqueueMessage()方法,enqueueMessage()方法最終內部會呼叫MessageQueue的enqueueMessage()方法,那現在我們就直接看MessageQueue中把訊息加入訊息佇列中的方法。

訊息的加入

當通過handler呼叫一系列方法如sendMessage()、sendMessageDelayed()方法時,最終會呼叫MessageQueue的enqueueMessage()方法。現在我們就來看看,訊息是怎麼加入MessageQueue(訊息佇列)中去的。

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;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;//頭部訊息
            boolean needWake;
            //如果佇列中沒有訊息,或者當前進入的訊息比訊息佇列中的訊息等待時間短,那麼就放在訊息佇列的頭部
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
	            //判斷喚醒條件,當前當前訊息佇列頭部訊息是屏障訊息,且當前插入的訊息為非同步訊息
	            //且當前訊息佇列處於無訊息可處理的狀態
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //迴圈遍歷訊息佇列,把當前進入的訊息放入合適的位置(比較等待時間)
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //將訊息插入合適的位置
                msg.next = p; 
                prev.next = msg;
            }

			//呼叫nativeWake,以觸發nativePollOnce函式結束等待
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製程式碼

這裡大家肯定注意到了nativeWake()方法,這裡先不對該方法進行詳細的描述,下文會對其解釋及介紹。 其實通過程式碼大家就應該發現了,在將訊息加入到訊息佇列中時,已經將訊息按照等待時間進行了排序。排序分為兩種情況(這裡圖中的message.when是與當前的系統的時間差):

  • 第一種:如果佇列中沒有訊息,或者當前進入的訊息比訊息佇列中頭部的訊息等待時間短,那麼就放在訊息佇列的頭部

第一種情況.png

  • 第二種:反之,迴圈遍歷訊息佇列,把當前進入的訊息放入合適的位置(比較等待時間)
    第二種情況.png

綜上,我們瞭解了在我們使用Handler傳送訊息時,當訊息進入到MessageQueue(訊息佇列)中時,已經按照等待時間進行了排序,且其頭部對應的訊息是Loop即將取出的訊息。

獲取訊息

我們都知道訊息的取出,是通過Loooper.loop()方法,其中loop()方法內部會呼叫MessageQueue中的next()方法。那下面我們就直接來看next()方法。

 Message next() {
	    
	    //如果退出訊息訊息迴圈,那麼就直接退出
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			
			//執行native層訊息機制層,
			//timeOutMillis引數為超時等待時間。如果為-1,則表示無限等待,直到有事件發生為止。
			//如果值為0,則無需等待立即返回。該方法可能會阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                //獲取系統開機到現在的時間,如果使用System.currentMillis()會有誤差,
                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) {//判斷當前訊息時間,是不是比當前時間大,計算時間差
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //沒有更多的訊息了
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                //判斷是否已經退出了
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                //獲取空閒時處理任務的handler 用於發現執行緒何時阻塞等待更多訊息的回撥介面。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                //如果空閒時處理任務的handler個數為0,繼續讓執行緒阻塞
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
				//判斷當前空閒時處理任務的handler是否是為空
                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; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
				//如果不儲存空閒任務,執行完成後直接刪除
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 重置空閒的handler個數,因為不需要重複執行
            pendingIdleHandlerCount = 0;
			
			//當執行完空閒的handler的時候,新的native訊息可能會進入,所以喚醒Native訊息機制層
            nextPollTimeoutMillis = 0;
        }
    }
複製程式碼

這裡大家直接看MessageQueue的next()法肯定會很懵逼。媽的,這個nativePollOnce()方法是什麼鬼,為毛它會阻塞呢?這個msg.isAsynchronous()判斷又是怎麼回事?媽的這個邏輯有點亂理解不了啊。大家不要慌,讓我們帶著這幾個問題來慢慢分析。

Native訊息機制

其實在Android 訊息處理機制中,不僅包括了Java層的訊息機制處理,還包括了Native訊息處理機制(與我們知道的Handler機制一樣,也擁有Handler、Looper、MessageQueue)。這裡我們不講Native訊息機制的具體程式碼細節,如果有興趣的小夥伴,請檢視----->深入理解Java Binder和MessageQueue

這裡我們用一張圖來表示Native訊息與Jave層訊息的關係(這裡為大家提供了Android原始碼,大家可以按需下載),具體細節如下圖所示:

Native訊息機制.png
(這裡我用的別人的圖,如有侵權,請聯絡我,馬上刪除)。

其實我們也可以從Java層中的MessageQueue中幾個方法就可以看出來。其中宣告瞭幾個本地的方法。

    private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    private native void nativePollOnce(long ptr, int timeoutMillis);
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
複製程式碼

特別是在MessageQueue構造方法中。

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();//mPtr 其實是Native訊息機制中MessageQueue的地址。
    }
複製程式碼

在Java層中MessageQueue在初始化的時候,會呼叫本地方法去建立Native MessageQueue。並通過mPrt儲存了Native中的MessageQueue的地址。

Native訊息機制與Java層的訊息機制有什麼關係

想知道有什麼關係,我們需要檢視frameworks\base\core\jni\android_os_MessageQueue.cpp檔案,

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
複製程式碼

其實nativeInit()方法很簡單,初始化NativeMessageQueue物件然後返回其地址。現在我們繼續檢視NativeMessageQueue的建構函式。

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}
複製程式碼

哇,我們看見了我們熟悉的"Looper",這段程式碼其實很好理解。Native Looper呼叫靜態方法getForThread(),獲取當前執行緒中的Looper物件。如果為空,則建立Native Looper物件。這裡大家肯定會有個疑問。當前執行緒是指什麼執行緒呢?想知道到底繫結是什麼執行緒,我們需要進入Native Looper中檢視setForThread()與getForThread()兩個方法。

getForThread()從執行緒中獲取設定的變數

/**
 * Returns the looper associated with the calling thread, or NULL if
 * there is not one.
 */
sp<Looper> Looper::getForThread() {
    int result = pthread_once(& gTLSOnce, initTLSKey);
    LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");
    return (Looper*)pthread_getspecific(gTLSKey);
}
複製程式碼

這裡pthread_getspecific()機制類似於Java層的ThreadLocal中的get()方法,是從執行緒中獲取key值對應的資料。其中通過我們可以通過註釋就能明白,Native Looper是儲存在本地執行緒中的,而對應的執行緒,就是呼叫它的執行緒,而我們是在主執行緒中呼叫的。故Native Looper與主執行緒產生了關聯。那麼相應的setForThread()也是對主執行緒進行操作的了。接著看setForThread()方法。

setForThread()從執行緒中設定變數

/**
  * Sets the given looper to be associated with the calling thread.
  * If another looper is already associated with the thread, it is replaced. *
  * If "looper" is NULL, removes the currently associated looper.
  */ 
void Looper::setForThread(const sp<Looper>& looper) {
    sp<Looper> old = getForThread(); // also has side-effect of initializing TLS
    if (looper != NULL) {
        looper->incStrong((void*)threadDestructor);
    }
    pthread_setspecific(gTLSKey, looper.get());
    if (old != NULL) {
        old->decStrong((void*)threadDestructor);
    }
}
複製程式碼

這裡pthread_setspecific()機制類似於Java層的ThreadLocal中的set()方法。通過註釋我們明白將Native looper放入呼叫執行緒,如果已經存在,就替換。如果為空就刪除。

nativePollOnce()方法為什麼會導致主執行緒阻塞?

經過上文的討論與分析,大家現在已經知道了,在Android訊息機制中不僅有 Java層的訊息機制,還有Native的訊息機制。既然要出裡Native的訊息機制。那麼肯定有一個處理訊息的方法。那麼呼叫本地訊息機制訊息的方法必然就是nativePollOnce()方法。

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
複製程式碼

在nativePollOnce()方法中呼叫nativeMessageQueue的pollOnce()方法,我們接著走。

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}
複製程式碼

這裡我們發現pollOnce(timeoutMillis)內部呼叫的是Natave looper中的 pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)方法。繼續看。

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                        this, ident, fd, events, data);
#endif
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        result = pollInner(timeoutMillis);
    }
}
複製程式碼

由於篇幅的限制,這裡就簡單介紹一哈pollOnce()方法。該方法會一直等待Native訊息,其中 timeOutMillis引數為超時等待時間。如果為-1,則表示無限等待,直到有事件發生為止。如果值為0,則無需等待立即返回。 那麼既然nativePollOnce()方法有可能阻塞,那麼根據上文我們討論的MessageQueue中的enqueueMessage中的nativeWake()方法。大家就應該瞭然了。nativeWake()方法就是喚醒Native訊息機制不再等待訊息而直接返回。

nativePollOnce()一直迴圈為毛不造成主執行緒的卡死?

到了這裡,其實大家都會有個疑問,如果當前主執行緒的MessageQueue沒有訊息時,程式就會便阻塞在loop的queue.next()中的nativePollOnce()方法裡,一直迴圈那麼主執行緒為什麼不卡死呢?這裡就涉及到Linux pipe/epoll機制,此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生,通過往pipe管道寫端寫入資料來喚醒主執行緒工作。這裡採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主執行緒大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。--摘自Gityuan知乎回答

如果大家想訊息瞭解Native 訊息機制的處理機制,請檢視----->深入理解Java Binder和MessageQueue

###屏障訊息與非同步訊息

屏障訊息

在next()方法中,有一個屏障的概念(message.target ==null為屏障訊息),如下程式碼:

  if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
複製程式碼

其實通過程式碼,當出現屏障的時候,會濾過同步訊息,而是直接獲取其中的非同步訊息並返回。如下圖所示:

屏障與非同步訊息.png

在Hadnler無參的建構函式中,預設設定的訊息都是同步的。那我們就可以知道在Android中訊息分為了兩種,一種是同步訊息,另一種是非同步訊息。在官方的解釋中,非同步訊息通常代表著中斷,輸入事件和其他訊號,這些訊號必須獨立處理,即使其他工作已經暫停。

非同步訊息

要設定非同步訊息,直接呼叫Message的setAsynchronous()方法,方法如下:

    /**
     * Sets whether the message is asynchronous, meaning that it is not
     * subject to {@link Looper} synchronization barriers.
     * <p>
     * Certain operations, such as view invalidation, may introduce synchronization
     * barriers into the {@link Looper}'s message queue to prevent subsequent messages
     * from being delivered until some condition is met.  In the case of view invalidation,
     * messages which are posted after a call to {@link android.view.View#invalidate}
     * are suspended by means of a synchronization barrier until the next frame is
     * ready to be drawn.  The synchronization barrier ensures that the invalidation
     * request is completely handled before resuming.
     * </p><p>
     * Asynchronous messages are exempt from synchronization barriers.  They typically
     * represent interrupts, input events, and other signals that must be handled independently
     * even while other work has been suspended.
     * </p><p>
     * Note that asynchronous messages may be delivered out of order with respect to
     * synchronous messages although they are always delivered in order among themselves.
     * If the relative order of these messages matters then they probably should not be
     * asynchronous in the first place.  Use with caution.
     * </p>
     *
     * @param async True if the message is asynchronous.
     *
     * @see #isAsynchronous()
     */
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }
複製程式碼

大家可以看到,設定非同步訊息,官方文件對其有詳細的說明,側面體現出了非同步訊息的重要性,那下面我就帶著大家一起來理一理官方的註釋說明。

  • 如果設定了非同步訊息,非同步訊息將不會受到屏障的影響(從next()方法中,我們已經瞭解了,當出現屏障的時候,同步訊息會直接被過濾。直接返回最近的非同步訊息)
  • 在某些操作中,例如檢視進行invalidation(檢視失效,進行重繪),會引入屏障訊息(也就是將message.target ==null的訊息放入訊息佇列中),已防止後續的同步訊息被執行。同時同步訊息的執行會等到檢視重繪完成後才會執行。

有哪些操作是非同步訊息呢?

這裡我就直接通過ActivityThread中的幾個非同步訊息給大家做一些簡單介紹。這裡我就不用程式碼展示了,用圖片來表示更清晰明瞭。

ActivityThread中的非同步訊息.png
在ActivityThread中,有一個sendMessage()多個引數方法。我們明顯的看出,有四個訊息是設定為非同步訊息的。DUMP_SERVICE、DUMP_HEAP、DUMP_ACTIVITY、DUMP_PROVIDER。從字面意思就可以看出來。回收service、回收堆記憶體、回收Activity、回收Provider都屬於非同步訊息。

屏障訊息傳送的時機

那麼Android中在哪些情況下會發生屏障訊息呢?其實最為常見的就是在我們介面進行繪製的時候,如在ViewRootImpl.scheduleTraversals()中。

 void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //傳送屏障訊息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
複製程式碼

在呼叫scheduleTraversals()方法時,我們發現會發生一個屏障過去。具體程式碼如下:

private int postSyncBarrier(long when) {
      
        synchronized (this) {
	        //記錄屏障訊息的個數
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;
			
			//按照訊息佇列的等待時間,將屏障按照順序插入
            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }
複製程式碼

這裡我們直接將圍欄放在了訊息佇列中,同時重要的是我們並沒有直接設定target,也就是tartget =null。其實現在我們可以想象,我們當我們正在進行介面的繪製的時候,我們是不希望有其他操作的,這個時候,要排除同步訊息操作,也是可能理解的。

IdleHandler(MessageQueuqe空閒時執行的任務)

在MessageQueue中的next()方法,出現了IdleHandler(MessageQueuqe空閒時執行的任務),檢視MessageQueue中IdleHander介面的說明:

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }
複製程式碼

當執行緒正在等待更多訊息時,會回撥該介面,同時queueIdl()方法,會在訊息佇列執行完所有的訊息等待且在等待更多訊息時會被呼叫,如果返回true,表示該任務會在訊息佇列等待更多訊息的時候繼續執行,如果為false,表示該任務執行完成後就會被刪除,不再執行。

其中MessageQueue通過使用addIdleHandler(@NonNull IdleHandler handler) 方法新增空閒時任務。具體程式碼如下:

 public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

複製程式碼

既然MessageQueue可以新增空閒時任務,那麼我們就看看最為明顯的ActivityThread中宣告的GcIdler。在ActivityThread中的H收到GC_WHEN_IDLE訊息後,會執行scheduleGcIdler,將GcIdler新增到MessageQueue中的空閒任務集合中。具體如下:

 void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            //新增GC任務
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
複製程式碼

ActivityThread中GcIdler的詳細宣告:

   //GC任務
   final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            //執行後,就直接刪除
            return false;
        }
    }
	// 判斷是否需要執行垃圾回收。
    void doGcIfNeeded() {
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
	    //獲取上次GC的時間
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc("bg");
        }
    }
複製程式碼

GcIdler方法理解起來很簡單、就是獲取上次GC的時間,判斷是否需要GC操作。如果需要則進行GC操作。這裡ActivityThread中還宣告瞭其他空閒時的任務。如果大家對其他空閒任務感興趣,可以自行研究。

什麼時候喚醒主執行緒呢?

通過上文的瞭解,大家已經知道了Native的訊息機制可能會導致主執行緒阻塞,那麼喚醒Native訊息機制**(讓Native訊息機制不在等待Native訊息,也就是nativePollOnce()方法返回)**在整個Android的訊息機制中尤為重要,這裡放在這裡給大家講是因為喚醒的條件涉及到屏障訊息與空閒任務。大家理解了這兩個內容後再來理解喚醒的時機就相對容易一點了,這裡我們分別對喚醒的兩個時機進行講解。

在新增訊息到訊息佇列中

boolean enqueueMessage(Message msg, long when) {
	    ...省略部分程式碼
        synchronized (this) {
	      ...省略部分程式碼
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;//頭部訊息
            boolean needWake;
            //如果佇列中沒有訊息,或者當前進入的訊息比訊息佇列中的訊息等待時間短,那麼就放在訊息佇列的頭部
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
	            //判斷喚醒條件,當前訊息佇列頭部訊息是屏障訊息,且當前插入的訊息為非同步訊息
	            //且當前訊息佇列處於無訊息可處理的狀態
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //迴圈遍歷訊息佇列,把當前進入的訊息放入合適的位置(比較等待時間)
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //將訊息插入合適的位置
                msg.next = p; 
                prev.next = msg;
            }

			//呼叫nativeWake,以觸發nativePollOnce函式結束等待
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製程式碼

上述程式碼,我們很明顯的看見Native訊息機制的喚醒,受到needWake這個變數影響,needWake ==true是在兩個條件下。

  • 第一個:如果當前訊息按照等待時間排序是在訊息佇列的頭部, needWake = mBlocked,且mBlocked會在當前訊息佇列中沒有訊息可以處理,且沒有空閒任務的條件下為true(mBlocked變數的賦值會在下文講解)。
  • 第二個:如果當前mBlocked=true(第一個條件判斷),且訊息佇列頭部訊息是屏障訊息,同時當前插入的訊息為非同步訊息的條件。needWake = true

在空閒任務完成的時候喚醒

 Message next() {
		 ...省略部分程式碼
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
	         ...省略部分程式碼
			//執行native層訊息機制層,
			//timeOutMillis引數為超時等待時間。如果為-1,則表示無限等待,直到有事件發生為止。
			//如果值為0,則無需等待立即返回。該方法可能會阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
	             ...省略部分程式碼
            
                //獲取空閒時處理任務的handler 用於發現執行緒何時阻塞等待更多訊息的回撥介面。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                //如果訊息佇列中沒有訊息可以處理,且沒有空閒任務,那麼就繼續等待訊息
                    mBlocked = true;
                    continue;
                }
			   ...省略部分程式碼
            }

	        ...省略執行空閒任務程式碼
	        
            // 重置空閒的handler個數,因為不需要重複執行
            pendingIdleHandlerCount = 0;
			
			//當執行完空閒的handler的時候,新的native訊息可能會進入,所以喚醒Native訊息機制層
            nextPollTimeoutMillis = 0;
        }
    }
複製程式碼

這裡我們可以看到 mBlocked = true的條件是在訊息佇列中沒有訊息可以處理,且也沒有空閒任務的情況下。也就是當前mBlocked = true會影響到MessageQueue中enqueueMessage()方法是否喚醒主執行緒。

如果當前空閒任務完成後,**會將nextPollTimeoutMillis 置為0,**如果nextPollTimeoutMillis =0,會導致nativePollOnce直接返回,也就是會直接喚醒主執行緒(喚醒Native訊息機制層)。

MessageQueue取出訊息整體流程

到目前為止,大家已經對整個訊息的傳送與取出有一個大概的瞭解了。這裡我著重對MessageQueue取訊息的流程畫了一個簡單的流程圖。希望大家根據對取訊息有個更好的理解。

整體流程.jpg

總結

  • Handler在發訊息時,MessageQueue已經對訊息按照了等待時間進行了排序。
  • MessageQueue不僅包含了Java層訊息機制同時包含Native訊息機制
  • Handler訊息分為非同步訊息同步訊息兩種。
  • MessageQueue中存在**“屏障訊息“**的概念,當出現屏障訊息時,會執行最近的非同步訊息,同步訊息會被過濾。
  • MessageQueue在執行完訊息佇列中的訊息等待更多訊息時,會處理一些空閒任務,如GC操作等。

感謝

站在巨人的肩膀上。可以看得更遠。該篇文章參閱了一下幾本圖書與原始碼。我這裡我給了百度雲盤的下載連結。大家可以按需下載。

深入理解Android 卷1,2,3

Android原始碼

Android開發藝術探索

相關文章