nativeWake详解
本文承接 nativePollOnce详解。上一文解释了 native Looper 如何等待;本文只追踪“谁决定必须唤醒、唤醒信号如何传到 epoll、信号被谁消费”。重点是区分三件事:Java 队列的 needWake 是业务判断,nativeWake() 是 JNI 转发,eventfd 写入只是让等待者重新检查,并不直接交付 Message。
1. Java判定
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
if (when >= mLast.when) {
needWake = needWake && mAsyncMessageCount == 0;
// 尾部插入
} else {
// 中间插入;遇到更早的异步消息后取消唤醒
}
}
if (needWake) {
nativeWake(mPtr);
}Java 只在唤醒可能改变“下一次可交付工作”时写 eventfd:新消息成为队头,或同步屏障在队头且新异步消息可能穿过屏障。Looper 正在执行回调时 mBlocked=false,入队不会额外唤醒;它会在当前回调结束后自然重新取队列。
2. 退出唤醒
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:quit(boolean)、resetForTest()
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
nativeWake(mPtr);退出不是为了让新消息变得可执行,而是把可能阻塞在 native poll 的线程叫醒,让 next() 看到 mQuitting 并返回 null。测试重置也会唤醒,因为清理了队列和 FD 记录后,等待线程需要重新读取状态。
3. JNI转发
源码文件:frameworks/base/core/jni/android_os_MessageQueue.cpp
相关函数:android_os_MessageQueue_nativeWake()、NativeMessageQueue::wake()
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();
}JNI 层不重新判断 Java 队列状态,也不读取 mMessages。它只把 Java 保存的 mPtr 转为 NativeMessageQueue*,再转发到该对象持有的 sp<Looper>。
4. 写入eventfd
源码文件:system/core/libutils/Looper.cpp
相关函数:Looper::wake()
void Looper::wake() {
uint64_t inc = 1;
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 "
"(returned %zd): %s", mWakeEventFd.get(),
nWrite, strerror(errno));
}
}
}每次唤醒写入一个 uint64_t 增量。eventfd 保存的是计数,不要求每次 Java 调用都对应一次独立 epoll 返回;多个写入可以合并,等待线程一次读取即可消费累计信号。TEMP_FAILURE_RETRY 处理被信号打断的 write;写入失败只有非 EAGAIN 才 fatal。
5. 注册wake
源码文件:system/core/libutils/Looper.cpp
相关函数:rebuildEpollLocked()
epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD,
mWakeEventFd.get(), &wakeEvent);
LOG_ALWAYS_FATAL_IF(result != 0,
"Could not add wake event fd to epoll instance: %s",
strerror(errno));wake FD 在 Looper 构造和 epoll 重建时都作为固定节点注册,使用保留的 WAKE_EVENT_FD_SEQ。epoll 通过序列号识别这是内部唤醒,而不是业务文件描述符。
6. 消费唤醒
源码文件:system/core/libutils/Looper.cpp
相关函数:pollInner()、awoken()
if (seq == WAKE_EVENT_FD_SEQ) {
if (epollEvents & EPOLLIN) {
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.",
epollEvents);
}
}
void Looper::awoken() {
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}awoken() 读取并清空 eventfd 计数。清空之后 pollInner() 仍会继续处理 native Message 和 FD response;Java MessageQueue.next() 返回后还会重新检查自己的链表。因此“读到 wake FD”与“取出 Java Message”之间至少隔着两层状态判断。
7. 不必唤醒
唤醒不是每次入队都需要。以下情况通常不写 eventfd:Looper 正在执行消息,插入的延迟消息没有改变队头到期时间,或者屏障后已经存在更早的异步消息。避免无效唤醒可以减少 epoll 返回和 Java 锁竞争,但 Java 链表仍会记录消息,下一轮自然会处理。
8. 并发合并
多个生产者可能在 Looper 阻塞期间连续调用 nativeWake()。eventfd 的计数语义允许这些写入合并:消费者一次 read() 取走累计值;唤醒协议要求的是“至少重新检查一次”,不是为每个生产者调用保留独立回调次数。消息数量和执行次数仍由 Java 链表决定。
9. 写入失败
eventfd 使用非阻塞模式。计数接近上限时,write 可能返回 EAGAIN;当前源码将它视为可忽略情况,因为 FD 已经处于可读状态,等待线程不需要额外的唤醒边沿。其他 write 错误或短写会进入 fatal 日志路径。TEMP_FAILURE_RETRY 只处理 EINTR 重试,不会把任意错误变成成功。
10. 退出边界
退出调用在设置 mQuitting、清理消息后写 wake;被唤醒的 MessageQueue.next() 先处理已经到期且仍保留的消息,然后检查退出标志,调用 dispose() 并返回 null。wake 本身不取消消息,也不关闭 Java 队列;清理和销毁由 Java MessageQueue 的退出路径负责。
11. 动手验证
rg -n "needWake|nativeWake\(|mQuitting|resetForTest" \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "nativeWake|NativeMessageQueue::wake" \
frameworks/base/core/jni/android_os_MessageQueue.cpp
rg -n "void Looper::wake|void Looper::awoken|mWakeEventFd|EAGAIN" \
system/core/libutils/Looper.cpp用四种输入阅读分支:队列阻塞时插入新队头、正在处理时插入消息、屏障队头插入异步消息、退出时唤醒。每种情况分别写出 needWake、eventfd 状态、pollInner() 返回后的下一步。
12. 边界说明
本文不展开 eventfd 内核计数上限的数值、epoll 的边沿/水平触发实现细节、pollAll() 循环或 Java MessageQueue 的完整排序。固定源码支持的是 needWake 判定、JNI 转发、eventfd 写入与读取,以及唤醒后的重新检查边界。
