消息處理機制之消息循環

Android引用程序的消息隊列創建完成之後,就會調用Looer類的loop()方法,進入到一個消息循環中。

在這裏插入圖片描述
按照流程圖分析android源碼的調用過程

  1. Looper:loop()
    frameworks/base/core/java/android/os/Looper.java
public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
        }
    }

首先獲取當前線程的消息隊列,接着會調用在無限for循環中獲取消息隊列中的消息。如果消息隊列中有消息存在,獲取的到msg就不爲null,否則就會在MessageQueue的next()方法中進入睡眠等待狀態,直到有新的新的消息要處理。如果獲取到的消息爲null,表明退出了消息隊列。

frameworks/base/core/java/android/os/MessageQueue.java

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        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();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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());
                }
                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 {
                        // Got a message.
                        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 {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

在函數調用nativePollOnce時線程可能進入休眠狀態,這取決於nextPollTimeoutMillis,如果其值等於爲0,表示即使消息隊列中沒有消息也不進入休眠狀態,如果其值大於0,表示需要休眠nextPollTimeoutMillis的時間,如果爲-1表示需要消息隊列中沒有消息需要處理,當前線程需要無期限的等待下去,直到被其它的線程喚醒爲止。

frameworks/base/core/jni/android_os_MessageQueue.cpp

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

MessageQueue的類成員函數nativePollOnce是一個JNI方法,它在android_os_MessageQueue中對應的方法爲android_os_MessageQueue_nativePollOnce。參數ptr指向的是MessageQueue.java中的成員變量mPtr,接着將其轉換爲一個NativeMessageQueue對象,然後調用NativeMessageQueue中的pollOnce方法,其會調用Looper.cpp的pollOnce方法。

system/core/libutils/Looper.cpp

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
       
       ...

        if (result != 0) {

            if (outFd != nullptr) *outFd = 0;
            if (outEvents != nullptr) *outEvents = 0;
            if (outData != nullptr) *outData = nullptr;
            return result;
        }

        result = pollInner(timeoutMillis);
    }
}

pollOnce方法中不斷檢測pollInner方法的返回值,如果不爲0,就會返回。

int Looper::pollInner(int timeoutMillis) {
 
    ...
    
    // Poll.
    int result = POLL_WAKE;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    ...

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd.get()) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
            ...
        }
    }
    ...
    
    return result;
}

當前線程在C++層創建了一個epoll實例,並將它的文件描述符保存在C++層Looper類的變量mEpollFd中,同時還將一個管道的讀端文件描述符註冊到它的裏面以便監聽這個管道的IO寫事件。

調用函數epoll_wait來監聽註冊在epoll實例中的IO讀寫事件,如果這些文件沒有發生IO讀寫事件,當前線程就會在epoll_wait中進入睡眠等待,等待的時間有timeoutMillis確定。

從函數epoll_wait返回後就會進入for循環,if語句會檢測發生IO讀寫事件的文件描述符是否與當前線程所關聯的一個管道端的文件描述符mWakeEventFd.get()。如果
是,並且發生的IO讀寫事件的類型爲EPOLLIN,那麼這時候說明其他線程向與當前線程相關聯的管道寫入了一個新的數據。接下來就會調用awoken()方法。

void Looper::awoken() {
    uint64_t counter;
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

在awoken中將調用函數read將當前線程相關聯的數據讀出來。

在這裏可以看出線程根本不關心寫入到管道中的內容,只是簡單的將其讀取出來,以清除管道中的舊數據。這樣就會在下一次消息循環時,監聽IO寫事件如果沒有新的消息需要處理就會進入睡眠等待狀態就會進入睡眠等待狀態,直到有其他線程向它的消息隊列中發送一個新消息爲止。

參考資料
《Android系統源代碼情景分析》羅昇陽 著

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章