Android Q消息循環:休眠、喚醒的底層原理及native層源碼分析

epoll簡介


Handler消息機制,在Java層的MessageQueue中,調用了native方法實現消息循環的休眠和喚醒(也就是線程的休眠和喚醒)。native方法nativePollOnce實現了消息循環的休眠,而方法nativeWake實現了消息循環的喚醒。

在native層,消息循環的休眠和喚醒使用了Linux內核的epoll機制和pipe。下面我們簡單介紹一下epool機制是什麼?

文件描述符

我們首先來介紹什麼是文件描述符?

在Linux系統中,把一切都看做是文件,當進程打開現有文件或創建新文件時,內核向進程返回一個文件描述符,文件描述符就是內核爲了高效管理已被打開的文件所創建的索引,用來指向被打開的文件,所有執行I/O操作的系統調用都會通過文件描述符。

內核利用文件描述符來訪問文件,文件描述符是非負整數。打開現存文件或新建文件時,內核會返回一個文件描述符,讀寫文件也需要使用文件描述符來指定待讀寫的文件。

pipe(管道)

pipe是Linux中最基本的一種IPC機制,可以用來實現進程、線程間通信。

特點:

  • 調用pipe系統函數即可創建一個管道。
  • 其本質是一個僞文件(實爲內核緩衝區)。
  • 由兩個文件描述符引用,一個表示讀端,一個表示寫端。
  • 規定數據從管道的寫端流入管道,從讀端流出。

Handler機制中的使用:

首先使用pipe創建兩個fd:writeFD、readFD。當線程A想喚醒線程B的時候,就可以往writeFD中寫數據,這樣線程B阻塞在readFD中就能返回。

也就是說,當文件描述符指向的文件(內核緩衝區)爲空時,則線程進行休眠。當另一個線程向緩衝區寫入內容時,則將當前線程進行喚醒。

epoll

Linux的select和poll實現了文件流的I/O監控,它可以同時觀察許多流的I/O事件,在空閒的時候,會把當前線程阻塞掉,當有一個或多個流有I/O事件時,就從阻塞態中醒來。但我們從select那裏僅僅知道了,有I/O事件發生了,並不知道事件是誰產生的,我們只能無差別輪詢所有文件流。

基於select和poll的性能問題,Linux使用了一種叫epoll的機制,epoll可以理解爲event poll,epoll會把哪個流發生了怎樣的I/O事件通知我們,系統內核中使用紅黑樹維護了一張表來負責維護文件流和事件的註冊列表。

epoll是在2.6內核中提出的,是select和poll的增強版。相對於select和poll來說,epoll更加靈活,沒有描述符數量限制。epoll使用一個文件描述符管理多個描述符,將用戶空間的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。epoll機制是Linux最高效的I/O複用機制,在一處等待多個文件句柄的I/O事件。

對於Handler中的Looper來說,所謂的I/O事件就是所監控的文件描述符上沒有有數據到達。

epoll事件類型

epoll支持四類事件:

  1. EPOLLIN(句柄可讀)
  2. EPOLLOUT(句柄可寫)
  3. EPOLLERR(句柄錯誤)
  4. EPOLLHUP(句柄斷)

epoll的使用:

  • epoll_create(int size):創建一個epoll對象,該方法生成一個epoll專用的文件描述符。它其實是在內核申請一空間,用來存放你想關注的fd上是否發生以及發生了什麼事件;size就是你在這個epoll fd上能關注的最大fd數。例如:epollfd = epoll_create(10)。
  • epoll_ctl(int epfd, int op, int fd, struct epoll_event * event):用於控制epoll文件描述符上的事件,可以註冊事件,修改事件,刪除事件。
    1. epfd:由epoll調用產生的文件描述符(epoll_create的返回值);
    2. op:操作的類型。例如註冊事件,可能的取值EPOLL_CTL_ADD(註冊)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(刪除);
    3. fd:op實施的對象(關聯的文件描述符);
    4. event:事件類型(指向epoll_event的指針);
    5. 如果調用成功返回0,失敗返回-1。
  • epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout):表示等待直到註冊的事件發生。
    1. 參數events用來從內核得到事件的集合。
    2. maxevents告之內核這個events有多大(數組成員的個數)。
    3. 參數timeout是超時時間(毫秒)。0表示立即返回,-1表示永久阻塞)。
    4. 該函數返回需要處理的事件數目,如返回0表示已超時。
    5. 返回的事件集合在events數組中,數組中實際存放的成員個數是函數的返回值。

需要注意的是,當創建好epoll句柄後,它就是會佔用一個fd值,可以在linux下/proc/進程id/fd/看到這個fd,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。

Android源碼分析(native層)


epoll相關類型

struct epoll_event結構定義:

struct epoll_event {
    uint32_t events;  /* epoll events (bit mask) */
    epoll_data_t data; /* User data */
};

我們用到的事件有:EPOLLIN(代表有數據可讀)、EPOLLOUT(代表有數據可寫)。

epoll_data_t的結構定義:

typedef union epoll_data {
    void *ptr;  /* Pointer to user-defined data */
    int fd;  /*File descriptor */
    uint32_t u32; /* 32-bit integer */
    uint64_t u64; /* 64-bit integer */
} epoll_data_t;

native層消息循環的初始化

從Java層我們調用了native方法nativePollOnce實現了消息循環的休眠,而方法nativeWake實現了消息循環的喚醒。

我們來看它們對應的native方法在哪裏註冊的:

base/core/jni/android_os_MessageQueue.cpp

static const JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
    { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
    { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
    { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
    { "nativeSetFileDescriptorEvents", "(JII)V",
            (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};

Java層獲取native層的指針

在Java層MessageQueue的構造函數中,調用了nativeInit()方法:

    private native static long nativeInit();
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

nativeInit()方法是一個native方法,它返回了一個int值,作爲native層在Java層的一個指針引用。

我們來看native層的實現。

android_os_MessageQueue_nativeInit方法:

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

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}
邏輯解析:
  1. 這裏新建了一個native層的消息隊列,NativeMessageQueue對象,其實native層也對應了一套Handler機制相關的類:Looper、MessageQueue、MessageHandler、Message。
  2. 添加到智能指針,強引用類型。
  3. 返回一個long值給java層,代表了該NativeMessageQueue對象,在java層調用native層時,作爲參數傳遞進來,然後轉換成NativeMessageQueue對象。
  4. NativeMessageQueue構造函數中,獲取一個線程的Looper對象,如果不存在在創建一個,並關聯到線程。這個操作跟Java層非常類似,不過Java層是Looper創建了MessageQueue,而native層正好相反。

Looper的初始化

我們來看native層Looper對象的構造函數:

/system/core/libutils/Looper.cpp

Looper::Looper(bool allowNonCallbacks)
    : mAllowNonCallbacks(allowNonCallbacks),
      mSendingMessage(false),
      mPolling(false),
      mEpollRebuildRequired(false),
      mNextRequestSeq(0),
      mResponseIndex(0),
      mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));//初始化一個管道,用於喚醒Looper
    LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));

    AutoMutex _l(mLock);
    rebuildEpollLocked();
}

初始化一個管道,用於喚醒Looper。mWakeEventFd是一個android::base::unique_fd類型,它的內部實現了pipe管道的創建。

rebuildEpollLocked方法:

void Looper::rebuildEpollLocked() {
    // 關閉舊的epoll實例
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        mEpollFd.reset();
    }

    // Allocate the new epoll instance and register the wake pipe.
    // 創建一個epoll實例並註冊喚醒管道
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    struct epoll_event eventItem;//epoll要監控的事件
    // 清空,把未使用的數據區域置0
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;// EPOLLIN可讀事件
    eventItem.data.fd = mWakeEventFd.get();//設置事件對應的fd
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);//喚醒管道添加到epoll監聽
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                        strerror(errno));

    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}

rebuildEpollLocked()方法負責創建一個epoll對象,然後將文件描述符事件添加到epoll監控,這裏添加的是可讀事件,當文件流可讀時,則喚醒線程。

消息循環的休眠實現

我們來看android_os_MessageQueue_nativePollOnce方法:

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);
}
  1. ptr是Java層傳遞進來的(在初始化時,native返回給java層的指針),將ptr轉換成對應的NativeMessageQueue對象。
  2. 然後,調用nativeMessageQueue->pollOnce()方法,把休眠時間作爲參數傳遞進去。

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

這裏調用了Looper對象的pollOnce方法實現線程的休眠操作。

Looper對象的pollOnce方法:

system/core/libutils/include/utils/Looper.h
system/core/libutils/Looper.cpp

    int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
    inline int pollOnce(int timeoutMillis) {
        return pollOnce(timeoutMillis, nullptr, nullptr, nullptr);
    }
    
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {// 先處理沒有Callback方法的Response事件
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {//表示沒有callback, 因爲POLL_CALLBACK的值是-2
                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 != nullptr) *outFd = fd;
                if (outEvents != nullptr) *outEvents = events;
                if (outData != nullptr) *outData = data;
                return ident;
            }
        }

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

        result = pollInner(timeoutMillis);
    }
}

邏輯解析:

這裏一個參數的pollOnce方法,調用了4個參數的pollOnce方法,除了休眠時間外,其他參數都爲nullptr。

首先定義了一個無限循環,在循環內部,先處理沒有Callback方法的Response事件。然後,調用pollInner進行文件描述符事件的處理。

pollInner方法:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {// 先處理沒有Callback方法的Response事件
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {//表示沒有callback, 因爲POLL_CALLBACK的值是-2
                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 != nullptr) *outFd = fd;
                if (outEvents != nullptr) *outEvents = events;
                if (outData != nullptr) *outData = data;
                return ident;
            }
        }

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

        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif

    // Adjust the timeout based on when the next message is due.
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

    // Poll.
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle. //即將處於閒置狀態
    mPolling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);//等待已註冊在epoll的文件描述符事件發生,如果有事件發生,則把事件返回到eventItems數組中。

    // No longer idling.
    mPolling = false; //不再處於閒置狀態

    // Acquire lock.
    mLock.lock();

    // Rebuild epoll set if needed.
    if (mEpollRebuildRequired) { //重置epoll
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

    // Check for poll error.
    if (eventCount < 0) {//錯誤處理
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
        result = POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {//沒有對應的事件發生,表示已超時返回
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = POLL_TIMEOUT;
        goto Done;
    }

    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif
    //處理所有的返回事件
    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 {//處理其他文件描述符事件
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));//處理request,生成對應的reponse對象,push到響應數組
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;

    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) { //處理native的Message消息,然後調用相應回調方法
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();

#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
                        this, handler.get(), message.what);
#endif
                handler->handleMessage(message);//執行消息處理
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            result = POLL_CALLBACK;//返回callback類型
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    //處理帶有Callback()方法的Response事件,執行Reponse相應的回調方法
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            // Invoke the callback.  Note that the file descriptor may be closed by
            // the callback (and potentially even reused) before the function returns so
            // we need to be a little careful when removing the file descriptor afterwards.
            int callbackResult = response.request.callback->handleEvent(fd, events, data);// 處理請求的回調方法
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq);
            }

            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();//清除reponse引用的回調方法
            result = POLL_CALLBACK;//返回callback類型
        }
    }
    return result;
}
邏輯解析:
  1. pollOnce方法中,首先計算出timeoutMillis的時間,也就是休眠的超時時間。
  2. 調用epoll_wait,等待已註冊在epoll的文件描述符事件發生,如果有事件發生,則把事件返回到eventItems數組中。如果無事件發生,則在線程此處進行休眠,直到監控的文件描述符事件發生爲止或超時或發生錯誤返回。
  3. 線程喚醒後,執行重置epoll(如果需要的話)。
  4. 執行epoll返回的錯誤處理。
  5. 如果epoll返回的事件列表爲空,則表示沒有對應的事件發生,表示已超時返回。
  6. 處理所有的返回事件。喚醒文件描述符相關事件是我們註冊監聽的喚醒事件,則執行awoken()操作,讀取當前線程關聯的管道的數據。如果存在其他文件描述符事件,則處理它們。
  7. 處理native的Message消息,然後調用相應回調方法。
  8. 處理帶有Callback()方法的Response事件,執行Reponse相應的回調方法。

awoken方法

void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken", this);
#endif

    uint64_t counter;
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

這裏將與當前線程所關聯的管道數據讀出來,以便可以清理這個管道的數據。TEMP_FAILURE_RETRY表示忽略系統中斷造成的錯誤。

線程喚醒的實現

Java層的MessageQueue中,(例如,enqueueMessage方法)調用了native方法nativeWake(mPtr)來實現消息循環的喚醒操作。我們來看native層是如何實現的。

nativeWake方法對應的native中的實現是android_os_MessageQueue_nativeWake方法:

base/core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
    mLooper->wake();
}

android_os_MessageQueue_nativeWake方法調用了NativeMessageQueue的wake(),NativeMessageQueue的wake()又調用了Looper的wake方法。

Looper的wake方法:

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
                             mWakeEventFd.get(), nWrite, strerror(errno));
        }
    }
}

這裏的實現非常簡單,只是向文件描述符mWakeEventFd中,寫入數據1即可。

這裏通常會在子線程中調用,在文件描述符mWakeEventFd中寫入數據後,通過epoll機制的監聽,在主線程端接收到該事件之後,就會喚醒主線程。

總結


本文從native層分析了Looper消息循環休眠和喚醒的實現原理。這也是UI線程進入Looper的無限循環之後,沒有ANR的原因,低層使用了Linux的epoll機制和pipe實現了線程在空閒時的休眠,並且不會佔用系統CPU資源。

本章重點內容如下:

  1. Handler消息機制,在Java層的MessageQueue中,調用了native方法實現消息循環的休眠和喚醒(也就是線程的休眠和喚醒)。native方法nativePollOnce實現了消息循環的休眠,而方法nativeWake實現了消息循環的喚醒。
  2. 在native層,消息循環的休眠和喚醒使用了Linux內核的epoll機制和pipe。
  3. 在Linux系統內核中,所有執行I/O操作的系統調用都會通過文件描述符。內核利用文件描述符來訪問文件,文件描述符是非負整數。
  4. pipe是Linux中最基本的一種IPC機制,可以用來實現進程、線程間通信。
  5. pipe其本質是一個僞文件(實爲內核緩衝區) ,它有兩個文件描述符引用,一個表示讀端,一個表示寫端,規定數據從管道的寫端流入管道,從讀端流出。
  6. Handler機制中,底層使用pipe創建兩個fd:writeFD、readFD。當線程A想喚醒線程B的時候,就可以往writeFD中寫數據,這樣線程B阻塞在readFD中就能返回。 也就是說,當文件描述符指向的文件(內核緩衝區)爲空時,則線程進行休眠。當另一個線程向緩衝區寫入內容時,則將當前線程進行喚醒。
  7. epoll機制是Linux最高效的I/O複用機制,在一處等待多個文件句柄的I/O事件。
  8. nativeInit()方法是一個native方法,它返回了一個int值,作爲native層在Java層的一個指針引用。
  9. Java層中MessageQueue的mPtr屬性,是native層的NativeMessageQueue對象的指針。
  10. native層也對應了一套Handler機制相關的類:Looper、MessageQueue、MessageHandler、Message,它們與Java層相對應。
  11. NativeMessageQueue構造函數中,獲取一個線程的Looper對象,如果不存在在創建一個,並關聯到線程。這個操作跟Java層非常類似,不過Java層是Looper創建了MessageQueue,而native層正好相反。
  12. Looper構造函數中,初始化了一個管道,用於喚醒Looper。mWakeEventFd是一個android::base::unique_fd類型,它的內部實現了pipe管道的創建。
  13. rebuildEpollLocked()方法負責創建一個epoll對象,然後將文件描述符事件添加到epoll監控,這裏添加的是可讀事件,當文件流可讀時,則喚醒線程。
  14. Looper對象的pollOnce方法實現線程的休眠操作,它的內部也定義了一個無限for循環,來處理native層的事件,以及通過epoll監控的文件描述符相關事件。
  15. 線程喚醒操作,是Looper的wake方法實現的,它只是向文件描述符mWakeEventFd中,寫入數據1即可。
  16. 線程喚醒後,調用awoken方法,清空文件描述符mWakeEventFd中的數據,以便重新使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章