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中的数据,以便重新使用。

猜你喜欢

转载自blog.csdn.net/u011578734/article/details/106241075
今日推荐