ALooper NDK
本文回答一个接口问题:NDK 代码调用 ALooper_prepare()、ALooper_addFd() 和 ALooper_pollOnce() 后,实际由哪个对象保存状态,事件如何从 epoll 结果变成 callback 或 ident,以及为什么新代码应优先使用 pollOnce() 而不是 pollAll()。
建议先阅读 epoll机制、NativeLooper addFd 和 NativeLooper sendMessage。它们分别解释底层等待、FD 请求替换和 Native 消息队列;本文只讲 NDK 对这套 Looper 的 C ABI 包装,不把 ALooper 当成另一套事件引擎。读者读完后应能定位公开头文件、包装实现和真实调用者,并判断某个线程能否使用 non-callback FD、某个 poll 返回值应由谁消费。
1. 不透明对象
源码文件:
frameworks/native/include/android/looper.hframeworks/base/native/android/looper.cpp
公共头文件只声明 struct ALooper,不暴露字段。实现文件把 ALooper* 与 android::Looper* 互转:
static inline Looper* ALooper_to_Looper(ALooper* alooper) {
return reinterpret_cast<Looper*>(alooper);
}
static inline ALooper* Looper_to_ALooper(Looper* looper) {
return reinterpret_cast<ALooper*>(looper);
}这不是复制或代理对象。ALooper* 指向的就是 android::Looper 实例,C API 只是隐藏 C++ 类型和实现细节。因而 ALooper 的事件表、wake eventfd、sequence 和 MessageEnvelope 都由同一个 libutils::Looper 持有。
2. 线程绑定
源码文件:
frameworks/base/native/android/looper.cppsystem/core/libutils/Looper.cpp
相关函数:ALooper_forThread()、ALooper_prepare()、Looper::prepare()
ALooper* ALooper_forThread() {
return Looper_to_ALooper(Looper::getForThread().get());
}
ALooper* ALooper_prepare(int opts) {
return Looper_to_ALooper(Looper::prepare(opts).get());
}真正的线程绑定在 Looper::prepare():它先读取当前线程的 thread_local Looper;没有时才创建并调用 setForThread()。因此一个线程最多关联一个 ALooper,ALooper_prepare() 不是每次调用都新建实例。
sp<Looper> Looper::prepare(int opts) {
bool allowNonCallbacks = opts & PREPARE_ALLOW_NON_CALLBACKS;
sp<Looper> looper = Looper::getForThread();
if (looper == nullptr) {
looper = sp<Looper>::make(allowNonCallbacks);
Looper::setForThread(looper);
}
if (looper->getAllowNonCallbacks() != allowNonCallbacks) {
ALOGW("Looper already prepared for this thread with a different value for the "
"LOOPER_PREPARE_ALLOW_NON_CALLBACKS option.");
}
return looper;
}已经存在 Looper 时,新的 opts 不会改变 mAllowNonCallbacks。也就是说,“先以 0 创建、后以 ALLOW_NON_CALLBACKS 准备”只会得到 warning,不能把旧实例变成支持 ident 返回的实例。
3. 引用所有权
源码文件:
frameworks/native/include/android/looper.hframeworks/base/native/android/looper.cpp
void ALooper_acquire(ALooper* looper) {
ALooper_to_Looper(looper)->incStrong((void*)ALooper_acquire);
}
void ALooper_release(ALooper* looper) {
ALooper_to_Looper(looper)->decStrong((void*)ALooper_acquire);
}ALooper_forThread() 和 ALooper_prepare() 返回的是当前线程关联对象的裸指针视图;它们不是跨线程传递的所有权凭证。头文件明确说明,只有把 ALooper 从一个线程安全交给另一个线程时,才需要 ALooper_acquire(),并在接收方完成使用后配对 ALooper_release()。
acquire/release 保护的是 Looper 对象本身,不会自动把调用线程切换到 Looper 所在线程,也不会让接收线程获得 thread-local 绑定。跨线程代码仍需通过 ALooper_addFd()、ALooper_wake() 等允许跨线程调用的接口协调工作。
4. 选项边界
源码文件:frameworks/native/include/android/looper.h
ALOOPER_PREPARE_ALLOW_NON_CALLBACKS 只控制是否允许 ALooper_addFd() 传入空 callback:
enum {
ALOOPER_PREPARE_ALLOW_NON_CALLBACKS = 1<<0
};callback 非空时,ident 会被底层忽略;callback 为空时,调用者必须传入非负 ident,并在 pollOnce() 或 pollAll() 返回后消费 outFd/outEvents/outData。如果 Looper 首次创建时没有该选项,后续 non-callback 注册会被底层拒绝。
5. FD注册
源码文件:
frameworks/native/include/android/looper.hframeworks/base/native/android/looper.cppsystem/core/libutils/Looper.cpp
int ALooper_addFd(ALooper* looper, int fd, int ident, int events,
ALooper_callbackFunc callback, void* data) {
return ALooper_to_Looper(looper)->addFd(fd, ident, events, callback, data);
}
int ALooper_removeFd(ALooper* looper, int fd) {
return ALooper_to_Looper(looper)->removeFd(fd);
}公共头文件的返回契约是:addFd() 成功返回 1,发生错误返回 -1;相同 FD 会替换旧注册。底层 Looper::addFd() 负责 callback/ident 校验、epoll_ctl(ADD/MOD) 和 sequence 表;NDK 层没有重新解释或转换这些结果。
removeFd() 返回 1 表示已删除,0 表示此前没有注册,-1 表示错误。删除返回后可以关闭 FD,但已经开始执行的 callback 可能仍在运行,或者某个已经 signal 的 response 还可能再执行一次;callback 自己返回 0 或在自身执行中注销后,才有“之后不再再次调用”的边界。
6. 两种消费
源码文件:frameworks/base/native/android/looper.cpp
ALooper_pollOnce() 从当前线程取 Looper,而不是从传入的 ALooper* 取对象:
int ALooper_pollOnce(int timeoutMillis, int* outFd,
int* outEvents, void** outData) {
sp<Looper> looper = Looper::getForThread();
if (looper == NULL) {
ALOGE("ALooper_pollOnce: No looper for this thread!");
return ALOOPER_POLL_ERROR;
}
IPCThreadState::self()->flushCommands();
return looper->pollOnce(timeoutMillis, outFd, outEvents, outData);
}这段代码有两个容易漏掉的边界:
pollOnce()没有 Looper 绑定时返回ALOOPER_POLL_ERROR,不是创建一个临时 Looper;- 真正阻塞前先调用
IPCThreadState::self()->flushCommands(),因此带 Binder 状态的线程会在进入等待前冲刷待发送命令。
callback 模式下,底层执行 callback 并返回 ALOOPER_POLL_CALLBACK;non-callback 模式下,底层返回非负 ident,并通过三个输出参数交给调用者。不能把 ALOOPER_POLL_CALLBACK 当作某个 FD 的 ident。
7. 轮询结果
源码文件:frameworks/native/include/android/looper.h
公开常量与 libutils::Looper 的负值一致:
| 返回值 | 触发条件 | 调用者动作 |
|---|---|---|
ALOOPER_POLL_WAKE (-1) | 被 ALooper_wake() 唤醒,且没有其他可交付结果 | 重新检查外部状态 |
ALOOPER_POLL_CALLBACK (-2) | 至少执行了一个 callback | 继续轮询或处理共享状态 |
ALOOPER_POLL_TIMEOUT (-3) | 超时且没有可交付结果 | 进入超时分支 |
ALOOPER_POLL_ERROR (-4) | 没有线程 Looper 或内部错误 | 处理错误,不读取 ident |
>= 0 | non-callback FD 就绪 | 读取输出参数并消费 FD |
头文件还提醒:任何结果都可能同时意味着 wake。循环不能只在返回值等于 ALOOPER_POLL_WAKE 时处理 wake;如果应用维护了跨线程退出标志,应在每次 pollOnce() 返回后都检查它。
8. 事件标志
源码文件:frameworks/native/include/android/looper.h
enum {
ALOOPER_EVENT_INPUT = 1 << 0,
ALOOPER_EVENT_OUTPUT = 1 << 1,
ALOOPER_EVENT_ERROR = 1 << 2,
ALOOPER_EVENT_HANGUP = 1 << 3,
ALOOPER_EVENT_INVALID = 1 << 4,
};INPUT 和 OUTPUT 是注册时请求的关注项;ERROR、HANGUP、INVALID 则由 Looper 始终通知,不需要出现在注册掩码中。真实 callback 不应只读 ALOOPER_EVENT_INPUT:例如 hid 的设备 callback 遇到 error/hangup 会通知上层并返回 0,让 Looper 注销该 FD。
源码文件:frameworks/base/cmds/hid/jni/com_android_commands_hid_Device.cpp
int Device::handleEvents(int events) {
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
mDeviceCallback->onDeviceError();
return 0;
}
// 读取 mFd 并处理 UHID 事件。
return 1;
}9. pollAll陷阱
源码文件:
frameworks/native/include/android/looper.hsystem/core/libutils/Looper.cpp
ALooper_pollAll() 会反复调用底层 pollAll(),直到遇到非 callback 结果;因此它不会把 callback 结果返回给调用者。问题在于公共头文件已将该 API 标记为 hidden/deprecated,并明确说明它不能可靠响应 ALooper_wake():如果同一轮同时处理另一个事件,wake 可能被吞掉。
int Looper::pollAll(int timeoutMillis, int* outFd,
int* outEvents, void** outData) {
if (timeoutMillis <= 0) {
int result;
do {
result = pollOnce(timeoutMillis, outFd, outEvents, outData);
} while (result == POLL_CALLBACK);
return result;
}
// 正超时版本继续 pollOnce,并扣除已经经过的时间。
}因此新代码应使用 ALooper_pollOnce() 循环,并把所有返回值都视为“可能伴随 wake”。pollAll() 仍保留是为了二进制兼容,不应据此把它当成更可靠的“处理全部事件”接口。
10. 共享路径
源码文件:
frameworks/base/core/jni/android_os_MessageQueue.cppframeworks/base/native/android/looper.cppsystem/core/libutils/Looper.cpp
Java MessageQueue 的 NativeMessageQueue 在当前线程没有 Looper 时直接创建 new Looper(false) 并绑定;之后 C++ NDK 代码调用 ALooper_prepare() 只会复用它并在选项不一致时告警。两条路径共享 epoll 实例,但消费方式不同:Java 侧通过 callback 注册 FD,NDK 侧既可以 callback,也可以使用 ident 输出。
“共享”只表示同一线程的 Native Looper 和 epoll 状态共享,不表示 Java Message 会变成 ALooper 消息,也不表示 NDK pollOnce() 可以从另一个线程直接消费 Java 主线程的 Looper。
11. 真实调用者
源码文件:frameworks/base/cmds/hid/jni/com_android_commands_hid_Device.cpp
hid::Device 在构造时先取当前线程 Looper;不存在时才用 ALOOPER_PREPARE_ALLOW_NON_CALLBACKS 创建,然后注册 FD callback:
ALooper* aLooper = ALooper_forThread();
if (aLooper == NULL) {
aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
}
ALooper_addFd(aLooper, mFd, 0, ALOOPER_EVENT_INPUT,
handleLooperEvents, reinterpret_cast<void*>(this));析构时它从当前线程取 Looper 并调用 ALooper_removeFd(),然后再销毁设备。这个顺序对应公共 API 的生命周期要求:先移除监听,再关闭或销毁 FD 关联资源;如果 callback 可能并发执行,Device 自己仍需保证 this 的生命周期。
源码文件:frameworks/base/native/android/sensor.cpp
传感器 NDK 也使用同一入口:ASensorManager_createEventQueue() 创建队列后把其 FD 注册到调用者提供的 ALooper,销毁队列时调用 ALooper_removeFd()。
ALooper_addFd(looper, queue->getFd(), ident,
ALOOPER_EVENT_INPUT, callback, data);
ALooper_removeFd(q->looper, q->getFd());这说明 ALooper 的主要消费者不是一套独立消息系统,而是把 NDK 资源的 FD 接入现有线程事件循环。
12. 调用模板
源码文件:frameworks/native/include/android/looper.h
callback 模式的最小控制流是:
ALooper* looper = ALooper_prepare(0);
ALooper_addFd(looper, fd, 0, ALOOPER_EVENT_INPUT, callback, data);
while (!stopping) {
int result = ALooper_pollOnce(-1, nullptr, nullptr, nullptr);
if (result == ALOOPER_POLL_ERROR) {
break;
}
// 每轮返回都重新检查 stopping;wake 可能与其他结果同时发生。
}
ALooper_removeFd(looper, fd);non-callback 模式则必须保留 ident 和输出参数:
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
ALooper_addFd(looper, fd, IDENT_DEVICE,
ALOOPER_EVENT_INPUT, nullptr, nullptr);
int outFd = -1;
int outEvents = 0;
void* outData = nullptr;
int ident = ALooper_pollOnce(-1, &outFd, &outEvents, &outData);
if (ident == IDENT_DEVICE) {
read(outFd, buffer, sizeof(buffer));
}第二个模板不能与 callback 模式混淆:callback 模式的返回值是负的 ALOOPER_POLL_CALLBACK,non-callback 模式才返回注册时的非负 ident。
13. 底层单测
源码文件:system/core/libutils/Looper_test.cpp
ALooper 包装层没有在当前源码范围内提供独立单测;其关键 FD 行为由它直接委托的 libutils::Looper 测试覆盖。non-callback 测试先向 pipe 写信号,再以 ident=5、空 callback 和自定义 data 注册接收端:
pipe.writeSignal();
mLooper->addFd(pipe.receiveFd, expectedIdent,
Looper::EVENT_INPUT, nullptr, expectedData);
int result = mLooper->pollOnce(100, &fd, &events, &data);
EXPECT_EQ(expectedIdent, result);
EXPECT_EQ(pipe.receiveFd, fd);
EXPECT_EQ(Looper::EVENT_INPUT, events);
EXPECT_EQ(expectedData, data);这组断言对应 ALooper non-callback 模式的 ident 与三个输出参数。另一组测试构造 Looper(false) 并传空 callback,断言 addFd() 返回 -1,固定了“已有 Looper 不允许 non-callback 时注册失败”的实现边界。重复注册测试则证明相同 FD 只调用替换后的 callback。
这些测试验证的是 ALooper 委托到的共享 Looper 行为,不覆盖 C ABI 符号、IPCThreadState::flushCommands() 调用或 pollAll() 的公开弃用声明;后三项应从固定实现和公共头文件核对。
14. 边界验证
可以用以下检索从公共 API 走到实现和调用者:
rg -n "ALooper_(prepare|forThread|pollOnce|pollAll|addFd|removeFd|wake)" \
frameworks/native/include/android/looper.h \
frameworks/base/native/android/looper.cpp
rg -n "ALOOPER_PREPARE_ALLOW_NON_CALLBACKS|ALooper_addFd|ALooper_removeFd" \
frameworks/base/cmds/hid/jni/com_android_commands_hid_Device.cpp \
frameworks/base/native/android/sensor.cpp
rg -n "new Looper\(false\)|getForThread\(\)|setFileDescriptorEvents" \
frameworks/base/core/jni/android_os_MessageQueue.cpp读者可以据此复述三条边界:已有 Looper 时 prepare 只复用并告警;无绑定 Looper 时 poll 返回错误;FD 删除后可以关闭描述符,但不能假设已经运行的 callback 被撤回。
15. 适用范围
本文覆盖 ALooper 公共头文件、frameworks/base 的 C ABI 包装和 libutils::Looper 的真实实现。它不把 ALooper_pollAll() 当作推荐 API,不推断未在源码中出现的设备运行时行为,也不展开传感器队列或 Java MessageQueue 的内部消费算法。对于需要处理 Binder 线程、Java 消息或 Native 定时消息的读者,应回到本文开头列出的专题,而不能仅凭 ALooper API 名称推导那些语义。
