这是一个系列文章,总共三篇分别讲:
一. 前言
本文以前两篇文章为基础,继续分析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多路复用:
-
select:初出茅庐
把想监控的文件描述集合通过函数告诉select拷贝到内核中,有性能损耗,为了减少损耗,限制数量不超过1024,select返回后我们仅仅能知道有些文件描述符可以读写了,但是我们不知道是哪一个,需要遍历查找。
-
poll:小有所成
poll相对于select的优化仅仅在于解决了文件描述符不能超过1024个的限制,select和poll都会随着监控的文件描述数量增加而性能下降,因此不适合高并发场景。
-
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_create ,epoll_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.sendXXX或Handler.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文件描述符将被打开为非阻塞模式。
小结:
- java层和native是差不多的逻辑,都是初始化
Looper和NativeMessageQueue,由此可见java层和naative层,是用的是两个不同的消息队列。 - 再Looper的构造函数中使用
epoll_create1创建新的epoll实例。 - 并且使用
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()
结合调用链我们分析得出
- 在java层当发送消息到Handler后,会执行
MessageQueue.enqueueMessage方法。 MessageQueue.enqueueMessage会根据msg的when插入消息链表中,并用needWake决定是否唤醒线程。- 如果需要唤醒则调用
nativeWake方法。 - 在
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()
结合调用链我们分析得出
- 当
Looper.loop开启循环时,会调用到JNI方法nativePollOnce。 nativePollOnce会不停的取数据,最终没有消息时会阻塞在epoll_wait,等待消息。这也是我们经常看到的ANR或者其他堆栈信息中Handler线程经常是阻塞在nativePollOnce方法,java层只能打印到这个方法了。- 在
native层调用epoll_wait进入挂起状态,同时释放资源,等待唤醒,等待超时时间为timeoutMillis。 - 如果这段时间没有消息过来,在到
timeoutMillis时间时会唤醒epoll_wait并向下继续执行。 - 当有消息过来,向
mWakeEventFd写入数据时,就会唤醒epoll_wait并向下继续执行。 - 当有消息时,执行
handler->handleMessage(message)处理消息。
总结
Looper.prepare方法既初始化了java层Looper和MessageQueue,也初始化了native层的Looper和NativeMessageQueue,用的是两个不同的消息队列。- 既然java层和native层都不是一个消息队列,那为什么还要调用JNI方法呢?我们可以看,这3个JNI方法,
nativeInit是初始化,nativeWake是唤醒线程,nativePollOnce是线程挂起。这样看我们就大致得到答案了:借助native层的epoll机制,实现了没有消息需要处理时,线程挂起,释放cpu等资源,减少无谓的性能消耗,当有消息需要处理时,及时唤醒线程进行处理。满足了Handler的使用需求,通过被动监听来触发处理事件,而不是消耗大量资源的阻塞I/O以及消耗CPU的轮询。 - java层有消息入队,要唤醒线程则调用
nativeWake方法,再调用nativeMessageQueue->wake()方法唤醒,native层也是调这个方法。 - 当
Looper.loo()启动后,就开启循环从消息队列中调用nativePollOnce取消息,如果没有消息就挂起等待有消息时的唤醒,如果有没到执行时间的消息就等当前时间和消息执行时间when时间差,epoll_wait超时后再取消息,传递给msg中的handler执行。
至此,整个Handler系列文章就结束了,相信你也有很多收获。