epoll 机制
面试重要度:⭐⭐⭐⭐⭐
考察频率:字节 80% | 阿里 75% | 腾讯 70%
一、核心概念
1.1 定义与作用
一句话定义: epoll 是 Linux 内核提供的高效 I/O 多路复用机制,Android 的 MessageQueue 在 Native 层通过 epoll 实现线程的精确阻塞与唤醒,使主线程在无消息时休眠不占用 CPU,有消息时能被及时唤醒。
为什么重要:
- 是 Handler 机制能够高效运行的底层基石
- 解释了"主线程 Looper 死循环为什么不会导致 ANR"这一经典面试题
- 理解 epoll 有助于深入理解 Android 消息机制的性能优势
- 字节跳动面试高频考点,区分候选人对底层原理的理解深度
epoll 在 Handler 中的角色:
Java 层 Native 层 Linux 内核
│ │ │
next() ──────────────→ nativePollOnce() ──────→ epoll_wait()
│ │ │
│ │ [线程休眠]
│ │ │
nativeWake() ←──────── Looper::wake() ←──────── eventfd 写入
│ │ │
[取出消息] ←───────── epoll_wait 返回 ←──────── [线程唤醒]
1.2 与其他概念的关系
- 消息入队:enqueueMessage 插入消息后可能调用 nativeWake 唤醒(详见
./01-消息入队enqueueMessage.md) - 消息出队:next() 通过 nativePollOnce 阻塞等待消息(详见
./02-消息出队next().md) - 延迟消息:epoll_wait 的超时参数实现精确延迟唤醒(详见
./03-延迟消息处理.md) - 本文重点:Native 层 epoll 的初始化、阻塞、唤醒机制
二、核心原理
2.1 工作机制
整体流程:
创建 epoll 实例 → 注册 eventfd → epoll_wait 阻塞 → eventfd 写入唤醒 → 处理事件返回
关键组件:
| 组件 | 作用 | 创建时机 |
|---|---|---|
| epoll fd | epoll 实例的文件描述符 | Native Looper 构造时 |
| eventfd/pipe | 用于唤醒的文件描述符 | Native Looper 构造时 |
| epoll_wait | 阻塞等待事件 | next() 调用时 |
| write(eventfd) | 触发唤醒 | nativeWake() 调用时 |
三种阻塞模式:
| timeoutMillis | 行为 | 触发场景 |
|---|---|---|
| 0 | 不阻塞,立即返回 | 有消息待处理 |
| -1 | 无限阻塞 | 队列为空 |
| > 0 | 阻塞指定毫秒 | 等待延迟消息到期 |
2.2 源码分析
2.2.1 Native Looper 初始化
// Android 11 源码:system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(0),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
// 步骤1:创建 eventfd(用于唤醒)
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);
// 步骤2:重建 epoll 实例
rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
// 步骤3:创建 epoll 实例
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
// 步骤4:将 eventfd 注册到 epoll
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN; // 监听可读事件
eventItem.data.fd = mWakeEventFd.get();
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll: %s", strerror(errno));
// 步骤5:注册其他文件描述符(如 InputChannel)
for (size_t i = 0; i < mRequests.size(); i++) {
// ... 注册自定义 fd
}
}
源码解读:
eventfd:Linux 提供的轻量级事件通知机制,比 pipe 更高效EFD_NONBLOCK:非阻塞模式,读写不会阻塞EPOLLIN:监听可读事件,当有数据写入 eventfd 时触发epoll_create1:创建 epoll 实例,返回文件描述符
2.2.2 阻塞等待 - pollOnce
// Android 11 源码:system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
// 处理已有的响应(来自自定义 fd 的事件)
while (mResponseIndex < mResponses.size()) {
// ... 处理响应
}
if (result != 0) {
return result;
}
// 核心:调用 pollInner 进行 epoll_wait
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
// 步骤1:调整超时时间
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;
}
}
// 步骤2:准备 epoll_event 数组
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 步骤3:核心!epoll_wait 阻塞等待
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 步骤4:处理唤醒后的状态
mPolling = false;
mLock.lock();
// 步骤5:检查是否需要重建 epoll
if (mEpollRebuildRequired) {
mEpollRebuildRequired = false;
rebuildEpollLocked();
goto Done;
}
// 步骤6:处理错误
if (eventCount < 0) {
if (errno == EINTR) {
goto Done; // 被信号中断,正常情况
}
result = POLL_ERROR;
goto Done;
}
// 步骤7:超时返回
if (eventCount == 0) {
result = POLL_TIMEOUT;
goto Done;
}
// 步骤8:处理事件
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) {
// 步骤9:清空 eventfd(消费唤醒事件)
awoken();
}
} else {
// 处理其他 fd 的事件(如 InputChannel)
// ...
}
}
Done:
// 步骤10:处理 Native 层的 Message
// ...
mLock.unlock();
return result;
}
源码解读:
-
epoll_wait是阻塞的核心,线程在此休眠 -
timeoutMillis参数控制阻塞时长 -
返回值
eventCount:> 0:有事件触发(被唤醒或有其他 fd 事件)= 0:超时返回< 0:错误(errno == EINTR 表示被信号中断)
2.2.3 唤醒机制 - wake
// Android 11 源码:system/core/libutils/Looper.cpp
void Looper::wake() {
uint64_t inc = 1;
// 向 eventfd 写入数据,触发 epoll_wait 返回
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: %s",
mWakeEventFd.get(), strerror(errno));
}
}
}
void Looper::awoken() {
uint64_t counter;
// 读取 eventfd,清空计数(消费事件)
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}
源码解读:
wake():向 eventfd 写入 1,触发 EPOLLIN 事件awoken():读取 eventfd 清空计数,防止重复触发TEMP_FAILURE_RETRY:宏,处理被信号中断的情况(EINTR)
2.2.4 Java 层调用链
// Android 11 源码:frameworks/base/core/java/android/os/MessageQueue.java
Message next() {
final long ptr = mPtr; // Native MessageQueue 指针
// ...
for (;;) {
// 调用 Native 层阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
// ...
}
}
// JNI 方法
private native void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
// Android 11 源码:frameworks/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);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
// 调用 Native Looper 的 pollOnce
mLooper->pollOnce(timeoutMillis);
// ...
}
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();
}
2.3 epoll vs select/poll 对比
| 特性 | select | poll | epoll |
|---|---|---|---|
| 最大 fd 数量 | 1024(FD_SETSIZE) | 无限制 | 无限制 |
| fd 传递方式 | 每次调用都要传递全部 fd | 每次调用都要传递全部 fd | 只需注册一次 |
| 事件检测方式 | 轮询所有 fd | 轮询所有 fd | 只返回就绪的 fd |
| 时间复杂度 | O(n) | O(n) | O(1) |
| 内核实现 | 数组 | 链表 | 红黑树 + 就绪链表 |
为什么 Android 选择 epoll:
- 高效:只处理就绪的事件,不需要遍历所有 fd
- 支持超时:精确的毫秒级超时控制
- 可扩展:支持监听多个 fd(如 InputChannel)
2.4 重要细节与边界条件
细节1:eventfd vs pipe
Android 6.0 之前使用 pipe,之后改用 eventfd:
// 旧版本(pipe)
int wakeFds[2];
pipe(wakeFds);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
// 新版本(eventfd)
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
eventfd 优势:
- 只需一个 fd(pipe 需要两个)
- 内核开销更小
- 语义更清晰
细节2:EPOLL_CLOEXEC 标志
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
EPOLL_CLOEXEC/EFD_CLOEXEC:fork 后自动关闭 fd- 防止子进程继承不需要的文件描述符
细节3:epoll_wait 返回 EINTR
if (eventCount < 0) {
if (errno == EINTR) {
goto Done; // 被信号中断,不是错误
}
}
- 信号可以中断 epoll_wait
- Android 中这是正常情况,重新循环即可
边界情况:重复唤醒
void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = write(mWakeEventFd.get(), &inc, sizeof(uint64_t));
// eventfd 会累加,多次 wake 只会触发一次 epoll_wait 返回
}
- 多次调用 wake() 不会导致多次唤醒
- eventfd 的计数器会累加,但只触发一次 EPOLLIN
- awoken() 会清空计数器
三、实际应用
3.1 典型场景
场景1:主线程消息循环
// ActivityThread.main()
Looper.prepareMainLooper();
Looper.loop(); // 内部 epoll_wait 阻塞
- 主线程大部分时间在 epoll_wait 休眠
- 有消息时被唤醒处理,处理完继续休眠
- CPU 占用极低
场景2:子线程 Handler
HandlerThread thread = new HandlerThread("worker");
thread.start();
Handler handler = new Handler(thread.getLooper());
handler.post(task); // nativeWake 唤醒子线程
- 子线程同样使用 epoll 阻塞
- post 消息触发 nativeWake
场景3:Input 事件分发
// InputDispatcher 将 InputChannel 注册到 Looper
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
- InputChannel 的 fd 注册到 epoll
- 有输入事件时 epoll_wait 返回
- 在同一个 epoll 实例中处理
3.2 性能特点
为什么 Looper 死循环不会卡死:
- 无消息时:epoll_wait(-1) 无限阻塞,线程进入休眠态,不占用 CPU
- 有消息时:epoll_wait 立即返回,处理消息
- 延迟消息:epoll_wait(timeout) 精确等待,到期自动唤醒
CPU 占用分析:
有消息:处理消息 → CPU 活跃(很短)
无消息:epoll_wait 休眠 → CPU 占用 0%
延迟等待:epoll_wait 休眠 → CPU 占用 0%
3.3 注意事项
1. Native Looper 扩展
// 可以注册自定义 fd 到 Looper
int Looper::addFd(int fd, int ident, int events,
Looper_callbackFunc callback, void* data);
- InputChannel 使用此机制接收输入事件
- SurfaceFlinger 使用此机制接收 VSync
2. 避免频繁唤醒
// 不推荐:频繁发送消息
for (int i = 0; i < 1000; i++) {
handler.post(task); // 每次都 nativeWake
}
// 推荐:合并消息或使用 sendMessage
handler.post(() -> {
for (int i = 0; i < 1000; i++) {
task.run();
}
});
四、面试真题解析
4.1 基础必答题
【高频题1】 主线程 Looper.loop() 是死循环,为什么不会导致 ANR?
标准答案(30秒) : 因为主线程的消息循环使用 epoll 机制实现高效阻塞。当没有消息时,线程通过 epoll_wait 进入休眠状态,不占用 CPU;当有消息或事件到达时(如 InputEvent、Handler 消息),通过 eventfd 唤醒线程处理。ANR 是指应用无法响应用户输入,而 Looper 阻塞时线程是可以被随时唤醒响应事件的。
深入展开:
阻塞的本质:
// Native 层
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 线程在这里休眠,不消耗 CPU
ANR 的真正原因:
- Input 事件 5 秒内未处理完成
- BroadcastReceiver 10 秒未执行完成
- Service 20 秒未响应
面试官追问:
-
追问1:epoll_wait 阻塞时线程处于什么状态?
- 答:线程处于 TASK_INTERRUPTIBLE(可中断睡眠)状态,被移出运行队列,不占用 CPU 时间片。
-
追问2:如何唤醒阻塞的主线程?
- 答:通过向 eventfd 写入数据触发 EPOLLIN 事件,epoll_wait 检测到事件后立即返回。
【高频题2】 nativePollOnce 和 nativeWake 是如何配合工作的?
标准答案(30秒) : nativePollOnce 调用 epoll_wait 阻塞当前线程等待事件;nativeWake 向 eventfd 写入数据触发 EPOLLIN 事件。当有新消息入队需要立即处理时,enqueueMessage 调用 nativeWake 唤醒阻塞的 next(),next() 被唤醒后取出消息返回给 Looper 处理。
深入展开:
// 阻塞
int Looper::pollInner(int timeoutMillis) {
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 被唤醒后检查是否是 wake 事件
if (fd == mWakeEventFd.get()) {
awoken(); // 清空 eventfd
}
}
// 唤醒
void Looper::wake() {
uint64_t inc = 1;
write(mWakeEventFd.get(), &inc, sizeof(uint64_t)); // 触发 EPOLLIN
}
面试官追问:
-
追问1:为什么用 eventfd 而不是 pipe?
- 答:eventfd 只需要一个 fd,而 pipe 需要两个;eventfd 内核开销更小,是专门为事件通知设计的。
-
追问2:多次调用 nativeWake 会多次唤醒吗?
- 答:不会。eventfd 的计数器会累加,但只触发一次 EPOLLIN 事件。awoken() 会读取清空计数器。
【高频题3】 epoll 相比 select/poll 有什么优势?
标准答案(30秒) : epoll 的优势主要有三点:一是时间复杂度 O(1),只返回就绪的 fd,不需要遍历所有 fd;二是 fd 只需注册一次,不用每次调用都传递;三是没有 fd 数量限制。这使得 epoll 在处理大量连接时效率远高于 select/poll。
深入展开:
| 对比项 | select/poll | epoll |
|---|---|---|
| 就绪检测 | 遍历所有 fd | 只返回就绪 fd |
| fd 传递 | 每次都要传递 | 只需注册一次 |
| 内核实现 | 线性扫描 | 红黑树 + 就绪链表 |
| 触发方式 | 仅水平触发 | 水平/边缘触发 |
面试官追问:
-
追问1:epoll 的水平触发和边缘触发有什么区别?
- 答:水平触发(LT)只要 fd 就绪就一直通知;边缘触发(ET)只在状态变化时通知一次。Android 使用水平触发。
-
追问2:Android 为什么选择水平触发?
- 答:水平触发更简单可靠,不会丢失事件。Handler 场景下性能差异不大,而边缘触发需要一次性处理完所有数据,实现更复杂。
【高频题4】 延迟消息是如何实现精确定时的?
标准答案(30秒) : 延迟消息依赖 epoll_wait 的超时参数实现精确定时。next() 计算队头消息的执行时间与当前时间的差值作为超时时间,传给 nativePollOnce。epoll_wait 会精确等待指定毫秒后自动返回,此时消息到期,取出执行。
深入展开:
// Java 层计算超时
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
nativePollOnce(ptr, nextPollTimeoutMillis);
// Native 层
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// eventCount == 0 表示超时返回
面试官追问:
-
追问1:如果等待期间有新消息入队怎么办?
- 答:如果新消息插入队头(when 更小),会调用 nativeWake 唤醒,重新计算超时时间。
-
追问2:精度能达到多少?
- 答:理论上毫秒级,但实际受系统调度影响,通常有几毫秒误差。
【高频题5】 MessageQueue 的 Native 层做了什么?
标准答案(30秒) : MessageQueue 在 Native 层主要做三件事:一是创建 epoll 实例和 eventfd;二是通过 epoll_wait 实现高效的线程阻塞;三是通过 eventfd 写入实现线程唤醒。此外,Native Looper 还支持注册其他文件描述符,用于接收 InputEvent 等系统事件。
深入展开:
// Native MessageQueue 初始化
NativeMessageQueue::NativeMessageQueue() {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false); // 创建 Native Looper
Looper::setForThread(mLooper);
}
}
// Native Looper 功能
class Looper {
unique_fd mEpollFd; // epoll 实例
unique_fd mWakeEventFd; // 唤醒用的 eventfd
// ...
};
面试官追问:
-
追问1:Java 层和 Native 层各有一个 Looper,它们是什么关系?
- 答:Java Looper 负责消息的调度分发,Native Looper 负责底层的阻塞/唤醒机制。Java MessageQueue 持有 Native MessageQueue 的指针(mPtr)。
-
追问2:InputEvent 是怎么传递到应用的?
- 答:InputChannel 的 fd 注册到 Native Looper,有输入事件时 epoll_wait 返回,通过回调将事件传递到 Java 层的 InputEventReceiver。
4.2 进阶加分题
【进阶题1】 分析:为什么 Android 6.0 后把 pipe 改成 eventfd?
参考答案:
改变原因:
-
资源占用
// 旧版本:pipe 需要两个 fd int wakeFds[2]; pipe(wakeFds); mWakeReadPipeFd = wakeFds[0]; mWakeWritePipeFd = wakeFds[1]; // 新版本:eventfd 只需要一个 fd mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); -
内核效率
- eventfd 是专门为事件通知设计的
- 没有 pipe 的缓冲区管理开销
- 更轻量的内核数据结构
-
语义更清晰
- eventfd 就是一个计数器,语义简单
- pipe 是通用的进程间通信机制,对于单纯的唤醒来说过于复杂
追问:eventfd 的计数器有什么作用?
- 答:计数器记录写入次数,读取时返回计数并清零。多次 wake() 累加计数但只触发一次 EPOLLIN,awoken() 读取时清空计数,保证下次 wake() 能正常触发。
【进阶题2】 Native Looper 如何支持监听多个文件描述符?
参考答案:
核心 API:
int Looper::addFd(int fd, int ident, int events,
Looper_callbackFunc callback, void* data) {
// 将 fd 注册到 epoll
struct epoll_event eventItem;
eventItem.events = events;
eventItem.data.fd = fd;
epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
// 保存回调信息
Request request;
request.fd = fd;
request.callback = callback;
mRequests.add(fd, request);
}
使用场景:
// InputChannel 注册
mLooper->addFd(mChannel->getFd(), 0, ALOOPER_EVENT_INPUT,
handleReceiveCallback, this);
// VSync 事件
mLooper->addFd(mDisplayEventReceiver->getFd(), 0, ALOOPER_EVENT_INPUT,
handleVsyncCallback, this);
追问:这种设计有什么优势?
- 答:统一的事件循环模型,所有事件(Handler 消息、Input 事件、VSync)都在同一个线程的 epoll 中处理,避免了多线程同步问题,简化了编程模型。
【进阶题3】 如果 epoll_wait 被信号中断(EINTR),Android 是如何处理的?
参考答案:
处理方式:
int Looper::pollInner(int timeoutMillis) {
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
if (eventCount < 0) {
if (errno == EINTR) {
goto Done; // 不是错误,正常返回
}
// 其他错误才报告
result = POLL_ERROR;
}
Done:
// 返回后外层循环会重新调用 pollOnce
return result;
}
设计思路:
- EINTR 表示系统调用被信号中断
- 这是正常情况,不需要特殊处理
- 返回后 Java 层的 next() 会重新循环调用 nativePollOnce
追问:什么信号会中断 epoll_wait?
- 答:最常见的是 SIGPROF(性能分析)、SIGCHLD(子进程状态改变)等。Android 系统会发送各种信号,epoll_wait 被中断是常见情况。
4.3 实战场景题
【场景题】 性能分析显示主线程 CPU 占用高,怀疑是消息过于频繁,如何排查和优化?
问题分析:
-
怀疑点1:消息发送过于频繁
- 大量 Handler.post() 导致频繁唤醒
-
怀疑点2:消息处理耗时
- 单个消息处理时间过长
-
怀疑点3:不必要的唤醒
- 消息合并不当
排查步骤:
// 1. 使用 Looper.setMessageLogging 监控消息
Looper.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
Log.d("Looper", x);
// 输出:>>>>> Dispatching to Handler ...
// <<<<< Finished to Handler ...
}
});
// 2. 使用 Systrace/Perfetto 分析
// 查看 MessageQueue 的 next() 和消息处理时间
// 3. 统计唤醒次数
// 在 nativeWake 处添加日志(需要修改源码或使用 hook)
优化方案:
// 方案1:消息合并
handler.removeMessages(MSG_UPDATE); // 移除旧的
handler.sendEmptyMessage(MSG_UPDATE);
// 方案2:防抖处理
handler.removeCallbacks(updateTask);
handler.postDelayed(updateTask, 100);
// 方案3:批量处理
List<Data> pendingData = new ArrayList<>();
handler.post(() -> {
for (Data data : pendingData) {
process(data);
}
pendingData.clear();
});
追问:
- 方案缺点?—— 可能导致数据延迟更新
- 如何监控唤醒频率?—— 使用 Systrace 的 MessageQueue 事件或自定义埋点
- 有没有系统级的优化?—— 考虑使用 Choreographer 批量处理 UI 更新
五、对比与总结
5.1 关键 API 对比
| API | 层级 | 作用 | 阻塞/唤醒 |
|---|---|---|---|
| nativePollOnce() | Java→Native | 调用 epoll_wait 阻塞 | 阻塞 |
| nativeWake() | Java→Native | 调用 write(eventfd) | 唤醒 |
| epoll_wait() | Native/内核 | 等待 epoll 事件 | 阻塞 |
| epoll_ctl() | Native/内核 | 注册/修改/删除 fd | - |
| eventfd() | Native/内核 | 创建事件通知 fd | - |
5.2 核心要点速记
一句话记忆: epoll 是 Handler 阻塞/唤醒的底层实现,通过 epoll_wait 休眠、eventfd 写入唤醒,实现主线程高效的消息等待机制。
3个关键点:
- 阻塞:epoll_wait 使线程休眠,不占用 CPU
- 唤醒:写入 eventfd 触发 EPOLLIN,epoll_wait 返回
- 超时:timeout 参数实现延迟消息的精确等待
面试官最爱问:
- Looper 死循环为什么不会 ANR?(epoll 阻塞是休眠,不占 CPU)
- nativePollOnce 和 nativeWake 如何配合?(epoll_wait + eventfd)
- 延迟消息如何实现?(epoll_wait 的 timeout 参数)
六、关联知识点
前置知识:
- 消息入队机制(详见:
./01-消息入队enqueueMessage.md) - 消息出队机制(详见:
./02-消息出队next().md) - 延迟消息处理(详见:
./03-延迟消息处理.md)
后续扩展:
- Linux epoll 深入:边缘触发、红黑树实现
- Android Input 系统:InputChannel 如何注册到 Looper
- VSync 机制:Choreographer 如何利用 Native Looper
相关文件:
./01-消息入队enqueueMessage.md- enqueueMessage 调用 nativeWake 的时机./02-消息出队next().md- next() 调用 nativePollOnce 的逻辑./03-延迟消息处理.md- timeout 参数如何计算