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

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

前言

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

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()方法,咱們接着往下走。async

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中把消息加入消息隊列中的方法。ide

消息的加入

當經過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是與當前的系統的時間差):oop

  • 第一種:若是隊列中沒有消息,或者當前進入的消息比消息隊列中頭部的消息等待時間短,那麼就放在消息隊列的頭部

第一種狀況.png

  • 第二種:反之,循環遍歷消息隊列,把當前進入的消息放入合適的位置(比較等待時間)
    第二種狀況.png

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

獲取消息

咱們都知道消息的取出,是經過Loooper.loop()方法,其中loop()方法內部會調用MessageQueue中的next()方法。那下面咱們就直接來看next()方法。ui

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()判斷又是怎麼回事?媽的這個邏輯有點亂理解不了啊。你們不要慌,讓咱們帶着這幾個問題來慢慢分析。this

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開發藝術探索

相關文章
相關標籤/搜索