epoll 机制

4 阅读15分钟

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 fdepoll 实例的文件描述符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 对比

特性selectpollepoll
最大 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 死循环不会卡死

  1. 无消息时:epoll_wait(-1) 无限阻塞,线程进入休眠态,不占用 CPU
  2. 有消息时:epoll_wait 立即返回,处理消息
  3. 延迟消息: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/pollepoll
就绪检测遍历所有 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?

参考答案

改变原因:

  1. 资源占用

    // 旧版本:pipe 需要两个 fd
    int wakeFds[2];
    pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    
    // 新版本:eventfd 只需要一个 fd
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    
  2. 内核效率

    • eventfd 是专门为事件通知设计的
    • 没有 pipe 的缓冲区管理开销
    • 更轻量的内核数据结构
  3. 语义更清晰

    • 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. 怀疑点1:消息发送过于频繁

    • 大量 Handler.post() 导致频繁唤醒
  2. 怀疑点2:消息处理耗时

    • 单个消息处理时间过长
  3. 怀疑点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个关键点

  1. 阻塞:epoll_wait 使线程休眠,不占用 CPU
  2. 唤醒:写入 eventfd 触发 EPOLLIN,epoll_wait 返回
  3. 超时:timeout 参数实现延迟消息的精确等待

面试官最爱问

  1. Looper 死循环为什么不会 ANR?(epoll 阻塞是休眠,不占 CPU)
  2. nativePollOnce 和 nativeWake 如何配合?(epoll_wait + eventfd)
  3. 延迟消息如何实现?(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 参数如何计算