Handler(三):Native层源码分析

116 阅读15分钟

这是一个系列文章,总共三篇分别讲:

Handler(一):基本原理和知识点

Handler(二):Java层源码分析

Handler(三):Native层源码分析

一. 前言

本文以前两篇文章为基础,继续分析Handler中的native方法。

主要重点分析JNI方法,关于native层handler,looper的使用和源码,先挖个坑,等有机会再分析。

后文源码基于Android API 35.

本文主要分三个部分:

1.一些基本概念和epoll。

2.几个native方法的分析。

3.总结。

二. 前置概念

我们先熟悉几个概念:文件描述符和eventfd。

1. 文件描述符

1.1 关于文件描述符fd

在linux上,一切都是以文件的形式存在的,设备、驱动等都是文件,比如binder驱动,这些文件都是以文件描述符fd来描述的,fd是一个非负整数,代表一个文件的编号,linux上每一个进程控制块PCB都会有一个文件描述符映射表,fd就是这个表的索引,这个表的每个项有一个指向已打开的文件的指针。所以我们可以将fd理解为文件、驱动、设备等。

1.2 Linux eventfd

EventFD 是 Linux 2.6.22 版本引入的一种轻量级进程间通信机制。它的本质是一个由内核维护的 64 位计数器,通过文件描述符的接口暴露给用户空间。是内核向用户空间的应用发送通知的机制,可以有效地被用来实现用户空间的事件/通知驱动的应用程序。通过将事件通知机制抽象为文件描述符,EventFD适用于 Linux 的 I/O 模型。

eventfd 是专门用来传递事件的 fd ,它提供的功能也非常简单:累计计数

int efd= eventfd();
write(efd, 1);//写入数字1
write(efd, 2);//再写入数字2
int res= read(efd);
printf(res);//输出值为 3

通过 write() 函数,我们可以向 eventfd 中写入一个 int 类型的值,并且,只要没有发生  操作,eventfd 中保存的值将会一直累加。

通过 read() 函数可以将 eventfd 保存的值读了出来,并且,在没有新的值加入之前,再次调用 read() 方法会发生阻塞,直到有人重新向 eventfd 写入值。

eventfd 实现的是计数的功能,只要 eventfd 计数不为 0 ,那么表示 fd 是可读的。再结合 epoll 的特性,我们可以非常轻松的创建出 生产者/消费者模型

epoll之前我们要先了解下什么是 I/O多路复用。

2. I/O多路复用

先从一个典型场景分析:

客户端要从 socket 中读数据,但是服务器还没有把数据传回来,这时候该怎么办? 通常有以下两种方案:

  • 阻塞: 线程阻塞到读数据方法,直到读到数据后再继续向下执行。
  • **非阻塞:**线程读取数据,如果没有数据则按固定方式进行轮询,直到读取到数据。

如果问题升级,同时发起100个网络请求,看看这两种方案各自会怎么处理?

  • 阻塞: 一个线程一次只能处理一个流的 I/O 事件,想要同时处理多个流,只能使用 多线程 + 阻塞 I/O 的方案。即开启多个线程,每个线程等待一个socket的数据,很明显浪费资源,cpu/内存资源使用率低。
  • **非阻塞:**一个线程可以轮询多个socket读数据,但是不停的轮询也比较消耗CPU资源。

阻塞非阻塞这两种方案,一旦有监听多个流事件的需求,我们只能选择,要么浪费线程资源(*阻塞型 I/O*要么浪费 CPU 资源(*非阻塞型 I/O* ,没有其他更高效的方案。

并且,这个问题在用户程序端是无解的,因为客户端只能主动去读取数据,主动读取这个操作就会浪费资源。 如果能够有一种机制或者称为监听/回调都行,能够被动的得到通知,就可以省去资源的浪费,提高效率。

这种能够让用户程序拥有 “同时监听多个流读写事件” 的机制,就被称为 I/O 多路复用!

I/O多路复用三剑客

在Linux世界中有这样三种机制可以用来进行I/O多路复用:

  1. select:初出茅庐

    把想监控的文件描述集合通过函数告诉select拷贝到内核中,有性能损耗,为了减少损耗,限制数量不超过1024,select返回后我们仅仅能知道有些文件描述符可以读写了,但是我们不知道是哪一个,需要遍历查找。

  2. poll:小有所成

    poll相对于select的优化仅仅在于解决了文件描述符不能超过1024个的限制,select和poll都会随着监控的文件描述数量增加而性能下降,因此不适合高并发场景。

  3. epoll:独步天下

    通过共享内存解决了频繁拷贝的问题。在select和poll机制下,进程要亲自下场去各个文件描述符上等待,任何一个文件描述可读或者可写就唤醒进程,然后进程需要遍历才知道哪个文件描述符可读或可写。epoll代替进程去各个文件描述符上等待,当哪个文件描述符可读或者可写的时候就告诉epoll,然后epoll唤醒进程,返回需要处理的文件描述符。

3. epoll

我们看下**epoll** 提供的三个函数:

int epoll_create(int size);
用于创建一个epoll对象
参数 size 是由于历史原因遗留下来的,自 Linux 2.6.8 以来,已不起作用,但必须大于零。
调用 epoll_create() 函数时,会进入到内核空间,创建并初始化一个 eventpoll 对象,把 eventpoll 对象映射到一个文件描述符,并返回这个文件描述符。
int epoll_ctl(int epollfd, int option, int fd, struct epoll_event *event);
option可以是下面类型:
	EPOLL_CTL_ADD 添加
	EPOLL_CTL_DEL 删除
	EPOLL_CTL_MOD 修改
event可以是下面类型:
	EPOLLIN 文件描述符可读
	EPOLLOUT 文件描述符可写
	EPOLLERR 文件描述符错误
	EPOLLHUP 文件描述符挂起
epoll_ctl() 用来执行 fd 的 “增删改” 操作,最后一个参数 event 是告诉内核 需要监听什么事件。还是以网络请求举例, socketfd 监听的就是 可读事件,一旦接收到服务器返回的数据,监听 socketfd 的对象将会收到 回调通知,表示 socket 中有数据可以读了。
int epoll_wait(int epollfd, struct epoll_event *events, int maxevents, int timeout);
epoll_wait()等待直到epollfd对应的事情发生. 是使用户线程阻塞的方法,它的第二个参数 events 接受的是一个 集合对象,如果有多个事件同时发生,events 对象可以从内核得到发生的事件的集合。

epoll + eventfd 作为消费者大部分时候处于阻塞休眠状态,而一旦有请求入队( *eventfd 被写入值*),消费者就立刻唤醒处理,Handler 机制的底层逻辑就是利用 epoll + eventfd,这里先大概分析一下:

首先消费者端,也就是Looper会调用epoll_createepoll_ctl创建epollfd,并注册给内核(此时会传入需要等待的事件类型),然后调用epoll_wait等待。

然后生产者端,也就是Handler发消息时,会创建eventfd并调用write写入数据,这时就会唤醒Looper中的epoll_wait(以返回值的形式),完成一次通信。

至此,有了 eventfd  、epoll 基础,接下来我们开始分析 Handler 的 Native 源码。

三. Handler 的 Native 源码

回顾一下我们之前分析的java层代码,以及遗留的native方法。

在异步通信准备阶段,通过Looper.prepare或者 Looper.prepareMainLooper方法,最终调用到nativeInit()方法,调用链如下:

Looper.prepare()
 └── new Looper()
      └── new MessageQueue()
           └── NativeMessageQueue::nativeInit()

在消息发送阶段,通过Handler.sendXXXHandler.postXXX方法发送的消息通过调用Handle.sendMessageAtTime最终调用到nativeWake()方法,调用链如下:

Handler.sendXXX()
 └── Handler.sendMessageDelayed()
      └── Handler.sendMessageAtTime()
           └── Handler.enqueueMessage()
                └── MessageQueue.enqueueMessage()
                     └── NativeMessageQueue::nativeWake()

在消息循环阶段,通过Looper.loop中开启了无限循环一直调用loopOnce方法,最终调用nativePollOnce方法获取消息,调用链如下:

Looper.loop()
 └── Looper.loopOnce()
      └── MessageQueue.next()
           └── NativeMessageQueue::nativePollOnce() //进入 Native 层

在android_os_MessageQueue.cpp中注册了上述几个native方法,我们一起分析一下。

frameworks/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 },
};

1. 异步通信准备阶段

主要看nativeInit()的实现。

// frameworks/base/core/jni/android_os_MessageQueue.cpp
// 可以看到nativeInit()对应实现为android_os_MessageQueue_nativeInit
static const JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    ......
};

// 看下android_os_MessageQueue_nativeInit实现
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    // 创建NativeMessageQueue对象
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }
    // 增加强引用计数,确保该对象不会被释放
    nativeMessageQueue->incStrong(env);
    // 将nativeMessageQueue的指针返回到java层。
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

// 下面再看下NativeMessageQueue的构造函数,
// 创建了Native的Looper对象。
// Native Handler相关的另起一篇文章单独介绍
NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    // 类似Java层的Looper.myLooper();
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        // 类似Java层的ThreadLocal.set();
        Looper::setForThread(mLooper);
    }
}
// system/core/libutils/Looper.cpp 
// 可以看到new Looper的构造函数
Looper::Looper(bool allowNonCallbacks)  : mAllowNonCallbacks(allowNonCallbacks),
      mSendingMessage(false), mPolling(false), mEpollRebuildRequired(false),
      mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1), mResponseIndex(0),
      mNextMessageUptime(LLONG_MAX) {
    //构造唤醒事件的fd  
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));

    AutoMutex _l(mLock);
    // 重建Epoll事件
    rebuildEpollLocked();
}

// 我们再看rebuildEpollLocked
void Looper::rebuildEpollLocked() {
    // 关闭旧的epoll实例,才能创建新实例
    if (mEpollFd >= 0) {
        mEpollFd.reset();
    }

    // 创建新的epoll实例,并注册wake管道
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
    epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
    // 将唤醒事件(mWakeEventFd)添加到epoll实例(mEpollFd)
	  int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
    for (const auto& [seq, request] : mRequests) {
        epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
        //将request队列的事件,分别添加到epoll实例
        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));
        }
    }
}
int epoll_create1(int flags);

epoll_create1其实是epoll_create的扩展版本,引入了一些额外的标志,使程序员可以更精细地控制事件文件描述符的行为。 flags:这是一个标志位集合,用于控制epoll实例的行为。常见的标志有: EPOLL_CLOEXEC:设置此标志后,创建的epoll文件描述符会在执行execve()时自动关闭。这是POSIX.1-2008中引入的新特性,旨在避免忘记在子进程中关闭文件描述符的问题。 EPOLL_NONBLOCK:这个标志在glibc中是别名,实际上是O_NONBLOCK。如果设置了这个标志,创建的epoll文件描述符将被打开为非阻塞模式。

小结:

  1. java层和native是差不多的逻辑,都是初始化LooperNativeMessageQueue,由此可见java层和naative层,是用的是两个不同的消息队列。
  2. 再Looper的构造函数中使用epoll_create1创建新的epoll实例。
  3. 并且使用epoll_ctl方法开启了监听,EPOLL_CTL_ADD表示监听计数增加,EPOLLIN表示文件描述符可读。

2. 消息发送

主要看nativeWake()的实现。

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    // 根据ptr取出对应的MessageQueue
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    // 调用wake()函数
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    // 直接调用了Looper的wake()
   mLooper->wake();
}

void Looper::wake() {
    uint64_t inc = 1;
    // 将唤醒事件的文件描述符mWakeEventFd写入inc,我们通过epoll机制知道,当写入数据的时候,读出端会立刻感知到,
    // 也就是说,此时读出端已经知道有数据写入了,也就是有message消息需要处理了,就在MessageQueue的next()里面会读到数据
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    //...
}
Handler.sendXXX()
 └── Handler.sendMessageDelayed()
      └── Handler.sendMessageAtTime()
           └── Handler.enqueueMessage()
                └── MessageQueue.enqueueMessage()
                     └── NativeMessageQueue::nativeWake()

结合调用链我们分析得出

  1. 在java层当发送消息到Handler后,会执行MessageQueue.enqueueMessage方法。
  2. MessageQueue.enqueueMessage会根据msgwhen插入消息链表中,并用needWake决定是否唤醒线程。
  3. 如果需要唤醒则调用nativeWake方法。
  4. nativeWake方法中对mWakeEventFd的文件描述符写入数据1,这样就会唤醒从mWakeEventFd读数据的阻塞线程读取到数据。

3. nativePollOnce方法

下面看下 nativePollOnce()的实现。

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {
    // 又是这一行,根据ptr取MessageQueue的
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    // 调用MessageQueue的pollOnce()
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    // 看这句就行,又是调用了Looper的pollOnce,看来native的MessageQueue就是个传话的,真正干事的是Looper,这难道就是传说中的代理模式  
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;
    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    // 死循环...
    for (;;) {
        // 死循环结束的条件是result != 0,那就是说如果等于0就一直跑
        if (result != 0) {
            //...
            return result;
        }

        // 等于0就一直跑这个,那就来看这个,pollInner其实是不会返回0的,那也就是说,只要pollInner有返回,就能结束循环
        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
    // 根据下一条消息到期的时间调整超时时间
    // ...
    /**
    result的取值有四种: 
    POLL_WAKE = -1  表示在 “超时时间到期” 之前使用 wake() 唤醒了轮询,通常是有需要立刻执行的新消息加入了队列
    POLL_CALLBACK = -2 表示多个事件同时发生,有可能是新消息加入,也有可能是监听的 自定义 fd 发生了 I/O 事件
    POLL_TIMEOUT = -3 表示设定的超时时间到期了
    POLL_ERROR = -4 表示错误
	  */
    int result = POLL_WAKE;
		// 创建事件集合eventItems,EPOLL_MAX_EVENTS=16
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 调用epoll_wait()来等待事件,如果有事件,就放入事件集合eventItems中,并返回事件数量,如果没有,就一直等,超时时间为我们传入的timeoutMillis
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
		// 加锁
    mLock.lock();

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

    // 如果发生的事件小于0,也就是说没有事件处理,就跳转到Done
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        result = POLL_ERROR;
        goto Done;
    }
    // 没有goto到Done,也就是有事件发生,就继续执行
    // 遍历事件集合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) {
            //事件类型为EPOLLIN(可读事件)
            if (epollEvents & EPOLLIN) {
                // 已经唤醒了,则读取并清空管道数据
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } 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 {
                ALOGW("Ignoring unexpected epoll events 0x%x for sequence number %" PRIu64 " that is no longer registered.", epollEvents, seq);
            }
        }
    }
    // 这里就是Done,是从if(eventCount<=0)跳转过来的
    Done:
    // 执行 native 消息分发
    mNextMessageUptime = LLONG_MAX;
    // mMessageEnvelopes是一个Vector,存放native层的消息
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //取出第一个MessageEnvelope,MessageEnvelop有收件人Hanlder和消息内容Message,可以理解为jave层的Message 
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        // 判断消息的执行时间,和java层的那个 if(message.when <= now)类似
        if (messageEnvelope.uptime <= now) {
            { // 表示消息已经到了执行时间
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                // handler 和message 取出后删除
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                // 释放锁
                mLock.unlock();
                // 处理消息
                handler->handleMessage(message);
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            // 结果赋值
            result = POLL_CALLBACK;
        } else {
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    // 执行自定义fd回调
    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;
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                AutoMutex _l(mLock);
                removeSequenceNumberLocked(response.seq);
            }
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}

void Looper::awoken() {
    uint64_t counter;
    // 把数据读取出来,根据EventFd的性质,使其可以重新使用
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

调用链

Looper.loop()
 └── Looper.loopOnce()
      └── MessageQueue.next()
           └── NativeMessageQueue::nativePollOnce() //进入 Native 层
                └── Looper::pollOnce()
                     └── Looper::pollInner()
                          └── epoll_wait()

结合调用链我们分析得出

  1. Looper.loop开启循环时,会调用到JNI方法nativePollOnce
  2. nativePollOnce会不停的取数据,最终没有消息时会阻塞在epoll_wait,等待消息。这也是我们经常看到的ANR或者其他堆栈信息中Handler线程经常是阻塞在nativePollOnce 方法,java层只能打印到这个方法了。
  3. native层调用epoll_wait 进入挂起状态,同时释放资源,等待唤醒,等待超时时间为timeoutMillis
  4. 如果这段时间没有消息过来,在到timeoutMillis时间时会唤醒epoll_wait并向下继续执行。
  5. 当有消息过来,向mWakeEventFd写入数据时,就会唤醒epoll_wait 并向下继续执行。
  6. 当有消息时,执行handler->handleMessage(message) 处理消息。

总结

  1. Looper.prepare 方法既初始化了java层Looper和MessageQueue,也初始化了native层的Looper和NativeMessageQueue,用的是两个不同的消息队列。
  2. 既然java层和native层都不是一个消息队列,那为什么还要调用JNI方法呢?我们可以看,这3个JNI方法,nativeInit是初始化,nativeWake是唤醒线程,nativePollOnce是线程挂起。这样看我们就大致得到答案了:借助native层的epoll机制,实现了没有消息需要处理时,线程挂起,释放cpu等资源,减少无谓的性能消耗,当有消息需要处理时,及时唤醒线程进行处理。满足了Handler的使用需求,通过被动监听来触发处理事件,而不是消耗大量资源的阻塞I/O以及消耗CPU的轮询。
  3. java层有消息入队,要唤醒线程则调用nativeWake方法,再调用nativeMessageQueue->wake()方法唤醒,native层也是调这个方法。
  4. Looper.loo()启动后,就开启循环从消息队列中调用nativePollOnce取消息,如果没有消息就挂起等待有消息时的唤醒,如果有没到执行时间的消息就等当前时间和消息执行时间when时间差,epoll_wait超时后再取消息,传递给msg中的handler执行。

至此,整个Handler系列文章就结束了,相信你也有很多收获。