Skip to content

NativeLooper sendMessage

从 Native Looper 的绝对时间入队追踪 MessageEnvelope、epoll 超时、锁外派发与按 handler 取消。

基于android-17.0.0_r1
AndroidNative LoopersendMessage定时消息源码阅读

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

Native 消息与 Java 消息不是同一个对象。头文件中的 Message 只有 what,MessageHandler 通过虚函数接收它;Looper 用 sp<MessageHandler> 保存消费者,因此待处理消息会持有 handler 的强引用。

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

cpp
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

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

cpp
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:当前线程还没有重新进入等待,处理完当前消息后会重新计算队头时间。

cpp
if (mSendingMessage) {
    return;
}

这不是“只允许 Looper 线程发送消息”。注释明确指出,即使发送发生在其他线程,只要 Looper 当前正在派发消息,下一步仍会重新计算等待时间,因此不需要额外唤醒。

6. 等待计算 ​

源码文件:system/core/libutils/Looper.cpp

相关函数:Looper::pollInner()

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

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

这里有三个关键时机:

  1. 先从队列删除 envelope,再执行用户 handler,避免当前消息重复派发;
  2. 复制局部 sp 后解锁,允许 handler 在回调中再次调用 Looper API,也避免用户代码阻塞 mLock;
  3. 回调返回后重新加锁并继续扫描,因此同一轮 pollInner() 可以连续处理多个已经到期的消息。

如果回调抛出 C++ 异常而未被上层处理,后续控制流不会按正常路径回到 mLock.lock();该实现假设 handleMessage() 遵守项目的无异常回调约定,本文不把未捕获异常外推为 Looper 的恢复机制。

8. 取消路径 ​

源码文件:

  • system/core/libutils/Looper.cpp
  • system/core/libutils/include/utils/Looper.h

相关函数:removeMessages(handler)、removeMessages(handler, what)

cpp
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。这只改变消费者引用策略,不能替代对普通强引用消息的注销。

cpp
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 消息:

cpp
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

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:

cpp
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. 阅读验证 ​

可以用以下检索把文章主线重新接回源码:

bash
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 规则直接推导。