native 层消息机制(一)

529 阅读4分钟

引言

我们知道,线程在没有消息可处理时会使用 MessageQueue#nativePollOnce() 陷入阻塞。当我们使用 Handler#sendMessage() 时又会通过 MessageQueue#nativeWake() 唤醒当前线程。

我们又知道,ims 通过 socket 将 touch 事件发往应用进程,应用进程收到后也会唤醒主线程,然后执行 InputEventReceiver#dispatchInputEvent(),并不会继续执行 MessageQueue#next。

现在有一个问题:touch 事件唤醒与通过 nativeWake() 的唤醒有什么区别?为什么主线程被 touch 事件唤醒后不会执行 MessageQueue#next?

MessageQueue

java 层的 MQ 在构造函数中会调用 nativeInit(),进入到 native 层。native 层做的事也很简单:创建一个 NativeMessageQueue 对象,并返回它的地址由 java 层 MQ#mPtr 记录。

NativeMessageQueue 在构造函数中会创建一个 Looper 对象,并使用 mLooper 指向该对象。注意这里 Looper 与 java 层的 Looper 没有任务关系

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    // 理解成 java 层的 ThreadLocal 即可
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

Looper 本身可以理解为对 epoll 机制的封装。它的构造函数如下:

Looper::Looper(bool allowNonCallbacks)
    : mAllowNonCallbacks(allowNonCallbacks),
      mSendingMessage(false),
      mPolling(false),
      mEpollRebuildRequired(false),
      mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),
      mResponseIndex(0),
      mNextMessageUptime(LLONG_MAX) {
    // 重置 mWakeEventFd
    // 这个 fd 很重要,往该 fd 中写入数据可以唤醒 looper
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    //...
    
    // 使用 epoll_create1,创建 epoll 句柄
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));

    epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
    // 将 mWakeEventFd 添加到 epoll 的监听列表中
    // 这样通过 mWakeEventFd 写入数据时就可以唤醒 epoll
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
    
    // 将一些在创建前就设置的 fd 也监听上,
    for (const auto& [seq, request] : mRequests) {
        epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
    }
}

上面就是整个 MessageQueue 的创建过程,下面看一下 java 层 MessageQueue 中一些方法的实现。

nativePollOnce 与 nativeWake

nativePollOnce() 在 MQ#next() 中调用,nativeWake() 在 MQ#enqueueMessage() 中调用。在 native 层两个方法都是调用了 NativeMessageQueue 的同名方法,再由 NativeMQ 调用到 Looper::pollOnce() 以及 Looper::wake()

Looper::pollOnce() 经过一系列调用到 pollInner(),这里面省略一些内容:

int Looper::pollInner(int timeoutMillis) {
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mPolling = true;
    
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 调用 epoll_wait,线程陷入等待。
    // 该方法结束时,eventCount 返回的是 epoll 监听的 fd 列表中有多少个 fd 有事件到来
    // 并会将 fd 放到传入的 eventItems 中
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    mPolling = false;
    
    // 遍历 eventItems,处理每一个事件
    for (int i = 0; i < eventCount; i++) {
        const SequenceNumber seq = eventItems[i].data.u64;
        uint32_t epollEvents = eventItems[i].events;
        if (seq == WAKE_EVENT_FD_SEQ) {
            // WAKE_EVENT_FD_SEQ = 1,对应的是 mWakeEventFd
            // 如果是通过 mWakeEventFd 唤醒的,就走该分支
            if (epollEvents & EPOLLIN) {
                // 判断成立,说明 mWakeEventFd 中有写入事件
                // 这个方法没有什么逻辑,主要就是将 mWakeEventFd 中写入的数据读出来
                awoken();
            } 
        } else {
            // 否则就是一些别的 fd
            const auto& request_it = mRequests.find(seq);
            if (request_it != mRequests.end()) {
                const auto& request = request_it->second;
                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;
                // 
                mResponses.push({.seq = seq, .events = events, .request = request});
            } else {
               
            }
        }
    }
    mLock.unlock();

    // 遍历处理所有的 response
    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;
            // 调用其 handleEvent() 方法
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                // handleEvent 返回 0,则将 request 移除掉,即下次再也收不到事件了
                removeSequenceNumberLocked(response.seq);
            } 
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}

pollInner 内容使用 epoll_wait 进行等待。epoll 被唤醒后会根据要唤醒的 fd 执行不同的逻辑:

  1. 如果是 mWakeEventFd 中有数据,那什么操作也不做,线程自然进行 MessageQueue#next() 中继续执行
  2. 否则就调用 handleEvent()。这一块逻辑后面看 addFd() 时再说。

下面再看 wake(),即 MessageQueue#enqueueMessage 中调用的。核心逻辑就一句话:

void Looper::wake() {
    // 通过 write() 向 mWakeEventFd 中写入数据
    // 结合上面的 pollInner() 可知,这边写入后 epoll 就会读到,进而唤醒线程
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
}

到此,native 层的基本逻辑已分析完毕,主要还是利用了 epoll 机制,所谓唤醒,也就是 epoll 预监听一个 fd,唤醒时往该 fd 中写入一个数据即可

addFd

我们还知道,ims 是 touch 事件的源头,它经过一系列处理后通过 socket 传给应用进程,它使用 socket 是 wms 创建的:wms 使用 socketpair() 创建一对套接在一起的 socket,一个给 InputDispatcher,一个返回给应用进程。ims 往留给 InputDispatcher 的 socket 中写入数据,那应用进程就可以收到。

现在看应用进程自己的处理,应用进程拿到 socket 后(当然,wms 并不是直接返回的 socket,只是最核心的还是 socket),最终会调用到 NativeInputEventReceiver::setFdEvents():

void NativeInputEventReceiver::setFdEvents(int events) {
     // 省略其余,最核心的就是将 socket 对应的 fd 添加到自己主线程的 Looper 中
     mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
}

下面就到了 Looper::addFd()

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    { 
        // 先给一个序列号。我们上面提到的 mWakeEventFd,它的序列号是 1
        const SequenceNumber seq = mNextRequestSeq++;

        // 封装成 request
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.events = events;
        request.callback = callback;
        request.data = data;

        epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
        auto seq_it = mSequenceNumberByFd.find(fd);
        if (seq_it == mSequenceNumberByFd.end()) {
            // 通过 epoll_ctl 向 epoll 中添加对 fd 的监听
            int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &eventItem);
            
        } else {
            
        }
    } // release lock
    return 1;
}

这就是 touch 事件能唤醒主线程的原因:因为主线程的 looper 也监听了 touch 事件对应的 socket。将 addFd() 与上面的 pollOnce() 结合着看,更好理解。

总结

到这里,native 层相关的逻辑已结束,核心还是 epoll

  1. 唤醒:epoll 预监听一个 fd,需要唤醒时向该 fd 中写数据即可