Skip to content

ALooper NDK

从 ALooper 公共头文件追踪 NDK 包装、线程绑定、pollOnce 返回值、FD 消费与 pollAll 的兼容边界。

基于android-17.0.0_r1
AndroidALooperNDKNative Looper源码阅读

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.h
  • frameworks/base/native/android/looper.cpp

公共头文件只声明 struct ALooper,不暴露字段。实现文件把 ALooper* 与 android::Looper* 互转:

cpp
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.cpp
  • system/core/libutils/Looper.cpp

相关函数:ALooper_forThread()、ALooper_prepare()、Looper::prepare()

cpp
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() 不是每次调用都新建实例。

cpp
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.h
  • frameworks/base/native/android/looper.cpp
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:

c
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.h
  • frameworks/base/native/android/looper.cpp
  • system/core/libutils/Looper.cpp
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* 取对象:

cpp
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);
}

这段代码有两个容易漏掉的边界:

  1. pollOnce() 没有 Looper 绑定时返回 ALOOPER_POLL_ERROR,不是创建一个临时 Looper;
  2. 真正阻塞前先调用 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
>= 0non-callback FD 就绪读取输出参数并消费 FD

头文件还提醒:任何结果都可能同时意味着 wake。循环不能只在返回值等于 ALOOPER_POLL_WAKE 时处理 wake;如果应用维护了跨线程退出标志,应在每次 pollOnce() 返回后都检查它。

8. 事件标志 ​

源码文件:frameworks/native/include/android/looper.h

c
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

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.h
  • system/core/libutils/Looper.cpp

ALooper_pollAll() 会反复调用底层 pollAll(),直到遇到非 callback 结果;因此它不会把 callback 结果返回给调用者。问题在于公共头文件已将该 API 标记为 hidden/deprecated,并明确说明它不能可靠响应 ALooper_wake():如果同一轮同时处理另一个事件,wake 可能被吞掉。

cpp
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.cpp
  • frameworks/base/native/android/looper.cpp
  • system/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:

cpp
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()。

cpp
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 模式的最小控制流是:

cpp
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 和输出参数:

cpp
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 注册接收端:

cpp
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 走到实现和调用者:

bash
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 名称推导那些语义。