Android Handler 机制全解:从源码看懂消息循环
从 Handler、Looper、MessageQueue、Message 四件套出发,结合 ActivityThread 启动链路与 nativePollOnce 底层实现,完整讲清 Android 主线程消息循环的设计与源码细节。
Handler 是 Android 里最基础、也最容易被误读的机制之一。很多人知道「Handler 发消息、Looper 循环取消息」,却不清楚:主线程为什么不会因 for(;;) 卡死?nativePollOnce() 到底在等什么?post(Runnable) 和 sendMessage() 在源码里走同一条路吗?
这篇从源码出发,把 Handler 四件套(Handler / Looper / MessageQueue / Message)串成一条完整链路。
一、整体架构:四件套如何协作
┌─────────────┐ enqueueMessage ┌──────────────────┐
│ Handler │ ────────────────────► │ MessageQueue │
│ (生产者/消费者) │ │ (单链表优先级队列) │
└─────────────┘ └────────┬─────────┘
▲ │ next()
│ dispatchMessage ▼
│ ┌──────────────────┐
└───────────────────────────────│ Looper │
│ for(;;) 事件循环 │
└──────────────────┘| 组件 | 核心职责 | 关键源码入口 |
|---|---|---|
| Message | 消息载体;what / obj / callback / target 等字段 | Message.obtain()、recycleUnchecked() |
| MessageQueue | 按 when 排序入队;next() 阻塞取队头 | enqueueMessage()、next()、nativePollOnce() |
| Looper | 持有一个 MessageQueue;loop() 死循环分发 | prepare()、loop()、ThreadLocal |
| Handler | 绑定 Looper;负责发送与处理消息 | sendMessage()、post()、dispatchMessage() |
一个线程最多一个 Looper,一个 Looper 对应一个 MessageQueue,可以有多个 Handler 绑定到同一个 Looper(它们共享同一条消息队列,由 Looper 串行分发)。
二、主线程 Looper 从哪来:ActivityThread 启动链
应用进程启动后,系统通过 ActivityThread.main() 进入主线程。简化后的关键路径:
// frameworks/base/core/java/android/app/ActivityThread.java(简化)
public static void main(String[] args) {
Looper.prepareMainLooper(); // ① 创建主线程 Looper
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq); // ② 绑定 Application、AMS 等
if (thread.mInstrumentation != null) {
thread.mInstrumentation.callApplicationOnCreate(...);
}
Looper.loop(); // ③ 进入消息循环,永不返回(正常情况)
throw new RuntimeException("Main thread loop unexpectedly exited");
}prepareMainLooper() 与普通的 prepare() 区别:
// frameworks/base/core/java/android/os/Looper.java(简化)
public static void prepareMainLooper() {
prepare(false); // allowQuit = false,主 Looper 不可 quit
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static void prepare() {
prepare(true); // 子线程 Looper 允许 quit
}
private static void prepare(boolean allowQuit) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(allowQuit));
}因此:
- 主线程在
ActivityThread.main()里自动有了 Looper,无需手动prepare() new Handler()或new Handler(Looper.getMainLooper())发到主线程的消息,最终都由这条Looper.loop()消费- 若
loop()意外返回,最后一行会直接抛异常——主线程事件循环退出 = 致命错误
主线程启动时序:
三、Looper 与 ThreadLocal:为什么 Handler 必须绑定 Looper
3.1 ThreadLocal 保存「当前线程的 Looper」
// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}Handler 构造时会检查当前线程是否有 Looper:
// Handler.java(简化)
public Handler(@Nullable Callback callback, boolean async) {
// ...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
}这就是为什么子线程直接 new Handler() 会崩溃——没有先 Looper.prepare()。
3.2 子线程正确使用 Handler 的模板
new Thread(() -> {
Looper.prepare(); // 创建 Looper 并放入 ThreadLocal
Handler handler = new Handler();
handler.post(() -> { /* 在此 Looper 线程执行 */ });
Looper.loop(); // 阻塞在此,开始消费消息
}).start();Looper.loop() 正常永不返回;调用 Looper.quit() / quitSafely() 后,next() 返回 null,循环退出。
3.3 loop():事件循环的核心
// Looper.java(AOSP 简化,保留关键逻辑)
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
final int thresholdOverride =
me.mSlowDeliveryThresholdMs != INVALID_THRESHOLD
? (int) me.mSlowDeliveryThresholdMs : -1;
for (;;) {
Message msg = queue.next(); // 可能阻塞
if (msg == null) {
return; // quit 时退出
}
// 慢消息日志(调试用途)
if (thresholdOverride >= 0) {
// logIfSlow(...)
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (thresholdOverride >= 0) {
// logIfSlow(...)
}
}
Binder.clearCallingIdentity();
}
}四、Message:对象池、字段含义与回收
4.1 字段一览
// Message.java(核心字段)
public final class Message {
public int what;
public int arg1;
public int arg2;
public Object obj;
Handler target; // 由 Handler.enqueueMessage 赋值
Runnable callback; // post(Runnable) 时设置
long when; // 执行时间点(uptimeMillis)
// 链表指针,MessageQueue 内部使用
Message next;
// 对象池
private static Message sPool;
private static int sPoolSize;
// ...
}| 字段 | 含义 |
|---|---|
what | 整型消息标识,配合 handleMessage() |
arg1 / arg2 | 轻量参数,避免为简单数据再装箱 obj |
obj | 任意对象载荷 |
callback | post(Runnable) 时非空,优先于 handleMessage 执行 |
target | 处理此消息的 Handler |
when | 绝对时间戳(SystemClock.uptimeMillis() 基准),决定队列顺序 |
4.2 对象池:obtain() 与 recycle
频繁创建 Message 会产生 GC 压力,因此框架用链表对象池复用:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}消息处理完毕后,Looper.loop() 分发结束会调用 msg.recycleUnchecked() 把 Message 放回池中。不要长期持有已入队的 Message 引用——回收后字段会被清空,且可能已被复用。
五、Handler 发送消息:从 API 到入队
5.1 post 与 sendMessage 殊途同归
// Handler.java
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
// ...
return enqueueMessage(msg, uptimeMillis);
}
private boolean enqueueMessage(Message msg, long when) {
if (msg.target != null && msg.target.getLooper() != mLooper) {
throw new IllegalArgumentException(
"Message must be sent to the Handler's Looper!");
}
if (msg.target != null) {
msg.target = this;
}
return mQueue.enqueueMessage(msg, when);
}结论:
post(Runnable)=obtain()一个 Message + 设置callback+when = now + delaysendMessage(Message)= 设置what/obj等 + 同样走enqueueMessage- 最终都进入同一个 MessageQueue
5.2 dispatchMessage:三种处理优先级
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg); // ① post(Runnable) 走这里
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; // ② Handler 构造时传入的 Callback
}
}
handleMessage(msg); // ③ 子类覆写 handleMessage
}
}
private static void handleCallback(Message message) {
message.callback.run();
}优先级:msg.callback(Runnable)> Handler.mCallback > handleMessage() 子类实现。
六、MessageQueue 源码:入队、出队与 nativePollOnce
6.1 数据结构:按 when 排序的单链表
MessageQueue 不是 java.util.Queue,而是按 when 升序排列的单链表,队头是最早该执行的消息。
// MessageQueue.java
Message mMessages; // 队头6.2 enqueueMessage:插入排序 + 唤醒
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
if (mQuitting) {
msg.recycle();
return false;
}
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 插到队头
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 若正在 nativePollOnce 阻塞,需要唤醒
} else {
// 按 when 找到合适位置插入中间或尾部
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr); // 唤醒 epoll_wait
}
}
return true;
}要点:
when == 0表示「立即执行」,会插到队头(仅次于同步屏障等特殊消息)- 插入时若 Looper 正阻塞在
nativePollOnce(),且新消息比当前等待的更早到期,则nativeWake()提前唤醒
6.3 next():阻塞等待的核心
@UnsupportedAppUsage
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = 0;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); // ★ 阻塞点
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// ... 同步屏障逻辑(见第八节)...
if (msg != null) {
if (now < msg.when) {
// 队头消息还没到时间,计算还要睡多久
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 取走队头
mBlocked = false;
prevMsg = msg;
msg = msg.next;
prevMsg.next = null;
return prevMsg;
}
} else {
nextPollTimeoutMillis = -1; // 无消息,无限期等待
}
// IdleHandler 回调(队列空闲时)
// ...
}
}
}nextPollTimeoutMillis 的取值逻辑:
| 值 | 含义 |
|---|---|
0 | 不阻塞,立即返回(刚被 nativeWake 唤醒时常见) |
> 0 | 阻塞最多 N 毫秒,等待延时消息到期 |
-1 | 无限期阻塞,直到有新消息入队并 nativeWake |
6.4 Native 层:epoll 与唤醒
Java 层的 nativePollOnce / nativeWake 在 JNI 中实现,底层使用 Linux epoll(早期版本为 pipe + epoll):
// frameworks/base/core/jni/android_os_MessageQueue.cpp(概念简化)
void NativeMessageQueue::pollOnce(int timeoutMillis) {
// ...
int eventCount = mEpollFd.modifyEvents(...);
// epoll_wait 阻塞,直到:新消息入队(nativeWake)、定时器到期、fd 事件
eventCount = mEpollFd.wait(timeoutMillis);
// ...
}
void NativeMessageQueue::wake() {
mWakeEventFd.write(1); // 向 eventfd 写入,打断 epoll_wait
}这就是为什么 for(;;) 不会占满 CPU:线程在 epoll_wait 中睡眠,由内核调度;有新消息时 nativeWake 写入 eventfd,线程被唤醒回到 next() 取消息。
七、阻塞语义:Java 层阻塞 vs Android 层事件驱动
很多人困惑:for(;;) 明明是死循环,为什么主线程还能响应触摸?
| 视角 | 结论 | 原因 |
|---|---|---|
| Java / 线程层面 | 同步阻塞 | nativePollOnce() 让线程在内核态挂起,几乎不占 CPU |
| Android / 应用层面 | 事件驱动、非忙等 | 有消息就处理,没消息就睡眠,随时可被 nativeWake 唤醒 |
| 日常开发语境 | 「阻塞」≈ 主线程执行耗时任务 | ANR / 掉帧来自 dispatchMessage() 执行太久,不是 nativePollOnce() 的等待 |
7.1 与 Terminal 程序的同构类比
// Terminal 程序
while (true) {
char* input = read(); // 阻塞等输入 → nativePollOnce()
process(input); // 处理 → dispatchMessage()
}// Android Looper
for (;;) {
Message msg = queue.next(); // 阻塞等消息
msg.target.dispatchMessage(msg); // 处理消息
}若主线程没有这个循环:ActivityThread.main() 执行完就返回 → 线程结束 → 进程退出 → App 闪退。Windows 消息循环、iOS RunLoop、Qt exec() 都是同一类设计。
7.2 两种「阻塞」不要搞混
// ✅ 正常:等待期间不占 CPU,新消息可唤醒
// Looper 在 nativePollOnce() 中睡眠
// ❌ 危险:dispatchMessage 执行太久
handler.post(() -> {
Thread.sleep(5000); // UI 无响应、ANR 风险
});| 阻塞类型 | 发生位置 | 后果 |
|---|---|---|
nativePollOnce() 等待 | 队列空 / 延时消息未到期 | 正常;线程存活且低功耗 |
dispatchMessage() 耗时 | Runnable.run() / handleMessage() | 掉帧、卡顿、ANR |
7.3 事件循环流程
八、进阶机制
8.1 IdleHandler:队列空闲时干活
当 MessageQueue 暂时没有可执行的消息(或需要等待下一批),会遍历 IdleHandler 列表:
public static interface IdleHandler {
boolean queueIdle();
}典型用途:在主线程空闲时预加载、批量 flush、延迟非紧急初始化。queueIdle() 返回 false 表示一次性,移除;true 则保留。
8.2 同步屏障(Sync Barrier)
MessageQueue 可以插入 target == null 的特殊屏障消息。屏障存在期间:
- 同步消息(默认
Message)暂停分发 - 异步消息(
Message.setAsynchronous(true))优先处理
Choreographer 利用此机制,让 VSYNC 帧回调(异步消息)优先于普通 UI 消息执行,减少掉帧。postSyncBarrier() / removeSyncBarrier() 成对使用。
// enqueueMessage 里判断异步消息是否需要唤醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();8.3 延时消息与定时精度
延时并非独立 Timer 线程,而是靠 next() 计算 nextPollTimeoutMillis = msg.when - now,在 nativePollOnce 中睡眠到指定时间或被新消息唤醒。精度受系统调度、主线程繁忙程度影响——主线程若长期被同步消息占满,延时消息会排队滞后。
九、内存泄漏:Message 引用链
9.1 泄漏成因
GC Root → MessageQueue → Message(延时未处理)
↓
Handler(非静态内部类)
↓
Activity(已 finish,仍被引用)非静态内部类 Handler 隐式持有外部 Activity 引用。若 postDelayed(..., 60_000) 后 Activity 已销毁,Message 仍在队列中 → Activity 无法回收。
9.2 修法
// ① 静态内部类 + 弱引用
private static class SafeHandler extends Handler {
private final WeakReference<Activity> ref;
SafeHandler(Activity activity) {
super(Looper.getMainLooper());
ref = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = ref.get();
if (activity == null) return;
// ...
}
}
// ② onDestroy 清理
@Override
protected void onDestroy() {
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}面试速查表见 Android 面试套路 的 Handler 章节。
十、与异步编程的边界
| 机制 | 解决什么 |
|---|---|
| Handler / Looper | 同一线程内消息排队、延时、切换执行时机;保证线程存活 |
| 线程池 / Coroutines / RxJava | 跨线程调度,把 IO 移出主线程 |
后台线程完成计算后更新 UI,仍需 handler.post { } 或 withContext(Dispatchers.Main)——因为 View 操作必须在主线程 Looper 上执行。异步范式演进见 异步编程思想的演进。
十一、常见追问速查
| 追问 | 源码级答案 |
|---|---|
| 一个线程能有几个 Looper? | 最多一个;prepare() 时 ThreadLocal 已有则抛异常 |
| 多个 Handler 共用一个 Looper? | 可以;它们共享 MessageQueue,由 loop() 串行分发 |
post 和 sendMessage 区别? | 都走 enqueueMessage;post 设置 msg.callback |
子线程 new Handler() 崩溃? | 未 Looper.prepare(),myLooper() 为 null |
| 如何退出子线程 Looper? | quit() / quitSafely() → next() 返回 null → loop() 结束 |
| 主线程能 quit Looper 吗? | prepareMainLooper() 设 allowQuit=false,正常不可退出 |
| 消息执行顺序? | 同一线程串行;when 相同则先入先出 |
nativePollOnce 做什么? | JNI 层 epoll_wait,无消息时阻塞线程,有消息时 nativeWake 唤醒 |
十二、总结
ActivityThread.main()
→ Looper.prepareMainLooper() // ThreadLocal 放入主 Looper + MessageQueue
→ ... 创建 Application / Activity ...
→ Looper.loop()
→ for (;;)
→ MessageQueue.next() // nativePollOnce 阻塞等待
→ Handler.dispatchMessage()
→ callback / handleMessageHandler 机制的本质:用「无限循环 + 内核级阻塞等待」让主线程活着且低功耗,用「单链表优先级队列 + 串行分发」保证 UI 操作的线程安全与时序可控。
从 Java 看,nativePollOnce() 是同步阻塞;从 Android 看,它是高效的事件驱动;从业务看,真正要担心的是 dispatchMessage() 里的耗时——而不是等待消息时的睡眠。