NativeLooper sendMessage
本文回答一个具体问题:C++ 线程调用 Looper::sendMessage() 后,消息如何进入 Native Looper,何时被 MessageHandler 消费,延迟消息如何改变 epoll_wait() 的超时,以及对象销毁前为什么必须调用 removeMessages()。
读者可以先阅读 epoll机制 和 NativeLooper addFd。前者解释 pollInner() 的等待与 response,后者解释同一个 Looper 中 FD 回调的另一条消费路径。本文不讨论 Java MessageQueue、同步屏障或 ALooper NDK 封装;这里的 Message 只包含一个 what,其消费者是 C++ 的 MessageHandler。
读完后,读者应能从 sendMessageDelayed() 定位到 MessageEnvelope 的插入位置,解释队头消息如何决定等待时间,并判断一次 removeMessages() 能否阻止已经开始执行的回调。
1. 对象边界
源码文件:
system/core/libutils/include/utils/Looper.hsystem/core/libutils/Looper.cpp
Native 消息与 Java 消息不是同一个对象。头文件中的 Message 只有 what,MessageHandler 通过虚函数接收它;Looper 用 sp<MessageHandler> 保存消费者,因此待处理消息会持有 handler 的强引用。
struct Message {
Message() : what(0) { }
Message(int w) : what(w) { }
int what;
};
class MessageHandler : public virtual RefBase {
public:
virtual void handleMessage(const Message& message) = 0;
};Message 不携带 Runnable、参数对象或 Java Handler。调用方必须把更丰富的状态放在自己的 handler 对象中,what 只负责选择处理分支。一个典型消费者是 incidentd 的 ReportHandler:它把 WHAT_TAKE_REPORT 和 WHAT_SEND_BROADCASTS 映射到两个 C++ 方法。
2. 三个入口
源码文件:system/core/libutils/Looper.cpp
相关函数:sendMessage()、sendMessageDelayed()、sendMessageAtTime()
void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now, handler, message);
}
void Looper::sendMessageDelayed(nsecs_t uptimeDelay,
const sp<MessageHandler>& handler, const Message& message) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now + uptimeDelay, handler, message);
}
void Looper::sendMessageAtTime(nsecs_t uptime,
const sp<MessageHandler>& handler, const Message& message) {
// 入队、排序和唤醒逻辑在这里统一完成。
}三条 API 的差异只在时间表达:即时消息取当前 CLOCK_MONOTONIC 纳秒;延迟消息把 delay 加到当前值;绝对时间入口直接接收目标时间。这样 pollInner() 不需要分别处理“立即”和“延迟”两套队列算法。
头文件要求 handler 非空,但三个入口没有 CHECK 或返回错误的分支。若调用方传入空 sp,消息仍可进入队列,到期后会在 handler->handleMessage() 处解引用空指针。因此“非空”是调用者必须维护的前置条件,不是 Looper 提供的失败恢复。
3. 时间队列
源码文件:system/core/libutils/include/utils/Looper.h
相关成员:MessageEnvelope、mMessageEnvelopes、mSendingMessage
struct MessageEnvelope {
MessageEnvelope() : uptime(0) { }
MessageEnvelope(nsecs_t u, sp<MessageHandler> h, const Message& m)
: uptime(u), handler(std::move(h)), message(m) {}
nsecs_t uptime;
sp<MessageHandler> handler;
Message message;
};
std::vector<MessageEnvelope> mMessageEnvelopes GUARDED_BY(mLock);
bool mSendingMessage GUARDED_BY(mLock);队列是按 uptime 升序排列的 std::vector,不是按 what 分组,也不是优先级队列。handler 的强引用保留在 envelope 中;消息从 vector 取出后,pollInner() 还会先复制一份局部 sp,保证回调执行期间 handler 不会因队列删除而析构。
4. 插入顺序
源码文件:system/core/libutils/Looper.cpp
相关函数:Looper::sendMessageAtTime()
size_t i = 0;
{
std::scoped_lock _l(mLock);
size_t messageCount = mMessageEnvelopes.size();
while (i < messageCount && uptime >= mMessageEnvelopes[i].uptime) {
i += 1;
}
MessageEnvelope messageEnvelope(uptime, handler, message);
mMessageEnvelopes.insert(mMessageEnvelopes.begin() + i, messageEnvelope);
}
if (i == 0) {
wake();
}条件使用 >=,所以相同时间的后到消息插在已有消息之后,保持 FIFO。新消息只有插入位置 i == 0 时才改变队头;插入队尾不会让 Looper 更早醒来。vector 插入会移动后续元素,复杂度为 O(n),这是一种针对 Native Looper 通常较短消息队列的直接实现。
5. 唤醒条件
源码文件:system/core/libutils/Looper.cpp
相关函数:Looper::sendMessageAtTime()、Looper::wake()
入队先持有 mLock,释放锁后才调用 wake()。锁外唤醒使其他线程能够继续入队,而不会把 eventfd 写入时间放在临界区内。唤醒的必要条件是“新消息成为队头”,因为只有这时已有的 epoll 超时可能过长。
若 Looper 正在 handleMessage() 中执行,mSendingMessage 为真,源码直接返回,不再写 eventfd:当前线程还没有重新进入等待,处理完当前消息后会重新计算队头时间。
if (mSendingMessage) {
return;
}这不是“只允许 Looper 线程发送消息”。注释明确指出,即使发送发生在其他线程,只要 Looper 当前正在派发消息,下一步仍会重新计算等待时间,因此不需要额外唤醒。
6. 等待计算
源码文件:system/core/libutils/Looper.cpp
相关函数:Looper::pollInner()
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;
}
}mNextMessageUptime 是上一次消息扫描留下的队头时间。pollInner() 将它转换成毫秒,与调用者给出的超时取更小值,再进入 epoll_wait()。因此“延迟消息已入队”并不等于立即执行:先由 wake() 让第一次 poll 重新计算,真正到期后才在后续 poll 中调用 handler。
7. 消息派发
源码文件:system/core/libutils/Looper.cpp
相关函数:Looper::pollInner()
mNextMessageUptime = LLONG_MAX;
while (!mMessageEnvelopes.empty()) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.front();
if (messageEnvelope.uptime <= now) {
sp<MessageHandler> handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.erase(mMessageEnvelopes.begin());
mSendingMessage = true;
mLock.unlock();
handler->handleMessage(message);
mLock.lock();
mSendingMessage = false;
result = POLL_CALLBACK;
} else {
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}这里有三个关键时机:
- 先从队列删除 envelope,再执行用户 handler,避免当前消息重复派发;
- 复制局部
sp后解锁,允许 handler 在回调中再次调用 Looper API,也避免用户代码阻塞mLock; - 回调返回后重新加锁并继续扫描,因此同一轮
pollInner()可以连续处理多个已经到期的消息。
如果回调抛出 C++ 异常而未被上层处理,后续控制流不会按正常路径回到 mLock.lock();该实现假设 handleMessage() 遵守项目的无异常回调约定,本文不把未捕获异常外推为 Looper 的恢复机制。
8. 取消路径
源码文件:
system/core/libutils/Looper.cppsystem/core/libutils/include/utils/Looper.h
相关函数:removeMessages(handler)、removeMessages(handler, what)
void Looper::removeMessages(const sp<MessageHandler>& handler) {
std::scoped_lock _l(mLock);
std::erase_if(mMessageEnvelopes,
[&](const MessageEnvelope& envelope) {
return envelope.handler == handler;
});
}
void Looper::removeMessages(const sp<MessageHandler>& handler, int what) {
std::scoped_lock _l(mLock);
std::erase_if(mMessageEnvelopes,
[&](const MessageEnvelope& envelope) {
return envelope.handler == handler
&& envelope.message.what == what;
});
}比较的是同一个 sp 指向的 handler 对象和 what 值。取消只处理仍在 mMessageEnvelopes 中的消息;已经被 pollInner() 删除并开始执行的消息不可能被撤回。取消后不主动 wake(),因为它只会移除工作,最坏结果是 Looper 仍按旧的、较早的唤醒时间醒来,再发现队头已改变。
9. 生命周期
源码文件:system/core/libutils/include/utils/Looper.h
头文件特别提醒:Looper 会在“有消息要投递时”持有 MessageHandler 的强引用,因此 handler 要销毁前应先调用 removeMessages()。
这条规则解决的是两个不同问题:
| 时机 | 队列中的 handler 引用 | 可执行动作 |
|---|---|---|
| 消息未到期 | envelope 持有强引用 | removeMessages() 删除 pending 消息 |
| 消息已取出、回调执行中 | 局部 sp 持有强引用 | 等待当前 handleMessage() 返回 |
| 所有消息处理完 | 无 Looper 持有的消息引用 | handler 可正常析构 |
WeakMessageHandler 是另一种选择:它保存 wp<MessageHandler>,派发时尝试 promote();目标对象已经释放时不再调用真实 handler。这只改变消费者引用策略,不能替代对普通强引用消息的注销。
void WeakMessageHandler::handleMessage(const Message& message) {
sp<MessageHandler> handler = mHandler.promote();
if (handler != nullptr) {
handler->handleMessage(message);
}
}10. 真实消费者
源码文件:frameworks/base/cmds/incidentd/src/IncidentService.cpp
ReportHandler 用 what 选择报告任务,并在合并新请求前先删除同类 pending 消息:
void ReportHandler::handleMessage(const Message& message) {
switch (message.what) {
case WHAT_TAKE_REPORT:
take_report();
break;
case WHAT_SEND_BROADCASTS:
send_broadcasts();
break;
}
}
void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
unique_lock<mutex> lock(mLock);
mBatch->addPersistedReport(args);
mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
}这里的取消不是为了停止已开始的 take_report(),而是把多个到达请求合并成一次“取当前 batch”的工作。sendMessageDelayed() 也用于延后发送 backlog 广播;因此 Native 消息的 what 既是分派标签,也是业务去重的筛选条件。
另一个消费者是输入系统的 PointerControllerContext。它在重置不活动计时器时先删除旧的 MSG_INACTIVITY_TIMEOUT,再按当前状态重新安排延迟消息;析构时则删除该 handler 的所有 pending 消息。
源码文件:frameworks/base/libs/input/PointerControllerContext.cpp
void PointerControllerContext::resetInactivityTimeoutLocked() {
mLooper->removeMessages(mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
nsecs_t timeout = mLocked.inactivityTimeout == InactivityTimeout::SHORT
? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT
: INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
mLooper->sendMessageDelayed(timeout, mHandler,
MessageHandler::MSG_INACTIVITY_TIMEOUT);
}
PointerControllerContext::~PointerControllerContext() {
mLooper->removeMessages(mHandler);
}这段代码把“重置计时器”具体化为取消旧 envelope、创建新 envelope,而不是修改队列中已有元素。读者调试超时重复触发时,应同时检查这两个调用以及 handleMessage() 的状态分支。
11. 单测输入
源码文件:system/core/libutils/Looper_test.cpp
测试中的 StubMessageHandler 只把收到的 Message 追加到 vector:
class StubMessageHandler : public MessageHandler {
public:
std::vector<Message> messages;
void handleMessage(const Message& message) override {
messages.push_back(message);
}
};关键测试覆盖四类边界:
| 输入 | 关键断言 | 能说明什么 |
|---|---|---|
立即发送一个 MSG_TEST1 | 下一次 pollOnce() 返回 POLL_CALLBACK,vector 含一个消息 | 即时消息会在下一轮派发 |
| 依次发送四条消息给两个 handler | 各 handler 的 what 顺序保持入队顺序 | 同一队列按时间和插入顺序消费 |
延迟 100ms,再连续调用 pollOnce() | 首次快速返回 POLL_WAKE 且未消费,随后约 100ms 后消费 | wake 负责重算,时间到期才派发 |
发送四条后删除 MSG_TEST3、MSG_TEST1 | 只收到 MSG_TEST2、MSG_TEST4 | 按 handler + what 的取消筛选生效 |
测试还显式覆盖过去时间和当前时间:负 delay、delay=0、now - 1s 都在下一轮执行,不会因为时间已经过去而进入特殊错误分支。
12. 阅读验证
可以用以下检索把文章主线重新接回源码:
rg -n "sendMessage(Delayed|AtTime)?|mMessageEnvelopes|mNextMessageUptime" \
system/core/libutils/include/utils/Looper.h \
system/core/libutils/Looper.cpp
rg -n "SendMessage|RemoveMessage|StubMessageHandler" \
system/core/libutils/Looper_test.cpp
rg -n "removeMessages|sendMessageDelayed|handleMessage" \
frameworks/base/cmds/incidentd/src/IncidentService.cpp \
frameworks/base/libs/input/PointerControllerContext.cpp阅读 pollInner() 时,可以尝试回答:如果新消息插入队尾,为什么不需要 wake()?如果 handler 在回调中销毁自己,哪一个 sp 保证回调返回前对象仍然存在?如果重置不活动计时器只调用 sendMessageDelayed() 而不先取消,测试或日志中会出现什么现象?
13. 边界
本文覆盖 libutils::Looper 的 Native 消息队列、时间排序、epoll 超时协作、handler 消费和取消/清理。它不把 Message 与 Java android.os.Message 视为跨语言共享对象,也不展开 Linux 时钟实现、Java 同步屏障或 ALooper 的兼容层;这些边界之外的行为不能从本文的 MessageEnvelope 规则直接推导。
