消息机制总览
本文面向已经能阅读 Java 方法、JNI 注册表和 C++ 类,理解线程与文件描述符基本概念的读者。建议先读 Java调用链 了解跨线程回调的阅读方法,再读 搜索调用链 练习从方法名追到 native 消费者。
本文只回答一个具体问题:一个线程已经进入 Looper.loop() 并在等待,另一个执行路径调用 Handler.post(r) 后,Runnable 如何回到绑定线程执行?答案不是“Handler 把任务放进队列”这么简单,而是一条跨层路径:Handler 创建带 callback 的 Message,MessageQueue 按 uptimeMillis 插入链表并决定是否唤醒,JNI 将 nativePollOnce 和 nativeWake 转给 NativeMessageQueue,native Looper 用 eventfd/epoll 睡眠或唤醒,Java Looper 取回消息后再调用 Handler.dispatchMessage。
读完后,你应能从 post() 定位到入队函数,解释消息为何需要 target,说明队列何时进入 native 阻塞,区分“入队成功”与“已经执行”,并能从退出、异常和清理路径继续搜索。
1. 对象边界
四个对象解决的是四个不同问题:Handler 保存发送者和消费者的绑定关系;Message 保存一次投递的载荷和目标;MessageQueue 保存待处理顺序与唤醒状态;Looper 在所属线程中反复取消息并调用目标。native Looper 不是 Java Looper 的父类,它只负责把消息队列的等待连接到文件描述符事件。
| 对象 | 所有者 | 关键状态 | 消费者 |
|---|---|---|---|
Handler | 创建它的应用或系统组件 | mLooper、mQueue、mCallback、mAsynchronous | dispatchMessage() |
Message | 当前一次队列投递 | target、callback、when、next | Looper.loop() |
MessageQueue | 一个 Java Looper | mMessages、mQuitting、mBlocked、mPtr | next() |
Java Looper | 绑定线程 | ThreadLocal、mQueue、循环状态 | loopOnce() |
native Looper | NativeMessageQueue | eventfd、epoll、fd requests | pollOnce() |
一个 Looper 只有一个 MessageQueue,但可以有多个 Handler。因此消息不是通过“属于哪个线程”的字段分发,而是通过 Message.target 找回具体 Handler;线程归属由 Handler 保存的 Looper 间接确定。
2. 线程准备
消息链开始前,线程必须先拥有 Looper。Looper.prepare() 使用静态 ThreadLocal,同一线程第二次调用会抛异常;它只创建 Looper 和 MessageQueue,不会自动开始循环。
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:prepare()、prepare(boolean)
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}new Looper(quitAllowed) 内部创建 MessageQueue;应用主线程的 Looper 通常由运行环境准备,业务代码只拿 Looper.getMainLooper()。自建工作线程则需要显式 prepare(),随后调用 loop(),否则 post() 只能入队而没有消费者。
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:Looper 构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}3. 消息入队
Handler.post(r) 并不直接保存一个 Runnable 到队列。它先通过 getPostMessage(r) 把 Runnable 放进 Message.callback,再沿 sendMessageDelayed、sendMessageAtTime 和 enqueueMessage 设置目标与时间。
源码文件:frameworks/base/core/java/android/os/Handler.java
相关函数:post()、sendMessageAtTime()、enqueueMessage()
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}这里有三个容易混淆的时机:post() 返回 true 只代表消息进入队列;when 使用 SystemClock.uptimeMillis 时间基准,深度睡眠不会计入时间;Looper 退出后,尚未到期的消息可能被丢弃。因此“入队成功”不是“Runnable 一定执行”。
4. 队列等待
Android 17 的源码树保留多个 MessageQueue 实现目录。本文按 LegacyMessageQueue 这条实现阅读:公共类负责 Java 链表和同步屏障,native 指针 mPtr 连接到 C++ NativeMessageQueue。
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关字段与函数:mPtr、mMessages、next()、enqueueMessage()
private long mPtr;
Message mMessages;
private boolean mQuitting;
private boolean mBlocked;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
mLooperThread = Thread.currentThread();
}消息进入链表时,队列比较 when,决定插入头部、内部还是尾部;如果 Looper 正在阻塞且新消息成为队头,队列调用 nativeWake。这个判断的目的不是“每次入队都唤醒”,而是只在当前等待条件已经失效时唤醒线程。
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null && !msg.isAsynchronous()) {
throw new IllegalStateException("Message must have a target");
}
synchronized (this) {
if (mQuitting) {
msg.recycleUnchecked();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) break;
if (needWake && p.isAsynchronous()) needWake = false;
}
msg.next = p;
prev.next = msg;
}
if (needWake) nativeWake(mPtr);
}
return true;
}队列中的 target == null 不是普通消息,而是同步屏障;它改变的是 next() 搜索可执行消息的规则。本文只用它解释唤醒判断,不把同步屏障的完整插入/移除机制混入这篇总览。
5. Native等待
当 Java 队列没有立即可执行的消息时,next() 根据下一条消息的时间计算 timeout,然后调用 nativePollOnce(mPtr, timeoutMillis)。JNI 注册表把这个 Java native 方法绑定到 NativeMessageQueue::pollOnce,没有经过 Binder。
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message msg = mMessages;
if (msg != null && now >= msg.when) {
mBlocked = false;
mMessages = msg.next;
msg.next = null;
msg.markInUse();
return msg;
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
}
}源码文件:frameworks/base/core/jni/android_os_MessageQueue.cpp
相关函数:android_os_MessageQueue_nativePollOnce()、android_os_MessageQueue_nativeWake()
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue =
reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue =
reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}JNI 层只做对象指针转换和方法转发;阻塞语义由 native Looper 决定。Java 队列中的时间排序和 C++ 中的 fd 事件是两套输入,pollOnce 返回后 Java 仍需重新检查消息链表。
源码文件:system/core/libutils/Looper.cpp
相关函数:Looper::Looper()、pollOnce()、pollInner()、wake()、awoken()
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mPolling(false),
mNextMessageUptime(LLONG_MAX) {
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
std::scoped_lock _l(mLock);
rebuildEpollLocked();
}
int Looper::pollInner(int timeoutMillis) {
int eventCount = epoll_wait(mEpollFd.get(), eventItems,
EPOLL_MAX_EVENTS, timeoutMillis);
if (eventCount == 0) return POLL_TIMEOUT;
for (int i = 0; i < eventCount; i++) {
if (eventItems[i].data.u64 == WAKE_EVENT_FD_SEQ) {
awoken();
}
}
return POLL_CALLBACK;
}
void Looper::wake() {
uint64_t inc = 1;
TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
}eventfd 是唤醒通道,epoll 是等待集合。新消息成为队头时,Java nativeWake() 最终写入 eventfd;epoll_wait 返回后 awoken() 读取计数,消费唤醒事件。唤醒只保证线程重新检查 Java 队列,不直接携带 Message 对象。
6. 回调分发
Java Looper.loop() 负责循环,不负责判断 Runnable 还是 Handler 子类。它从 MessageQueue.next() 获得消息,再调用 msg.target.dispatchMessage(msg);真正的三级分发在 Handler 中完成:优先 Message.callback,其次 Handler.Callback,最后 handleMessage。
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:loop()、loopOnce()
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called");
}
me.mInLoop = true;
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) return;
}
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();源码文件:frameworks/base/core/java/android/os/Handler.java
相关函数:dispatchMessageImpl()
public void dispatchMessageImpl(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null && mCallback.handleMessage(msg)) {
return;
}
handleMessage(msg);
}
}因此 post(r) 最终不会调用 handleMessage();它把 Runnable 放入 Message.callback,由第一分支执行。消息执行完成后,Looper 在 finally 路径回收 Message;如果 callback 抛出异常,异常会离开当前循环,进程是否继续由上层异常处理决定,不能把 post() 当作异常隔离机制。
7. 结束路径
正常路径是 enqueue → next → dispatch → recycle。非正常路径至少有三种:Looper 已退出时,enqueueMessage() 回收消息并返回 false;消息到期前调用 quitSafely() 时,队列保留已到期消息并丢弃未来消息;callback 抛异常时,当前 loop() 不会自动吞掉异常。
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:quit()、quitSafely()、removeAllMessagesLocked()
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) return;
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
nativeWake(mPtr);
}
}这解释了 Handler.post() 返回 false 的常见原因:绑定的 Looper 正在退出。调用者若需要保证任务完成,必须自己设计生命周期确认或取消协议;消息队列不会为已经返回 true 的消息提供完成回调。
8. 阅读实验
在 Android 源码 checkout 中,可以用下面的命令把主线重新定位:
rg -n "post\(|sendMessageAtTime|enqueueMessage|dispatchMessage" \
frameworks/base/core/java/android/os/Handler.java
rg -n "prepare\(|loop\(|loopOnce|dispatchMessage" \
frameworks/base/core/java/android/os/Looper.java
rg -n "nativePollOnce|nativeWake|enqueueMessage|Message next" \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java \
frameworks/base/core/jni/android_os_MessageQueue.cpp
rg -n "eventfd|epoll_wait|void Looper::wake|void Looper::awoken" \
system/core/libutils/Looper.cpp可以做两个最小实验。第一,在 Handler.post() 前后记录线程名和 SystemClock.uptimeMillis(),确认 callback 在线程绑定的 Looper 上执行,而不是发送线程立即执行。第二,在线程调用 quitSafely() 后再次 post(),记录返回值并检查未来消息是否执行;这个实验只覆盖 Java 队列的退出语义,不代表 native fd 注册或所有 MessageQueue 变体都相同。
9. 边界
本文追踪的是 Java Handler 消息投递到 native Looper 唤醒再返回 Java 分发的主线。没有展开同步屏障的完整 API、IdleHandler、HandlerThread、Binder 主线程调度、Choreographer 的 VSYNC 编排和 Combined/Deli MessageQueue 的差异;这些主题需要各自的源码入口,不能用本篇总览替代。
