Android Handler 机制与源码深度解析
Handler 是 Android 中最为核心的异步通信机制。在 Android 的世界里,几乎所有的事件驱动——无论是 UI 的更新、点击事件的回调,还是生命周期的调度,底层都离不开 Handler 机制。
在面试中,Handler 是高频考点。本文将配合源码,从核心组件、工作流程、底层原理到进阶特性,深度解析 Handler 的实现原理。
一、 Handler 机制的四大核心组件
Handler 机制的核心由四个类组成,它们共同协作完成消息的传递和处理:
- Message (消息):负责承载数据,是线程间传递的信息载体。
- Handler (处理者):负责发送消息 (
sendMessage) 以及处理消息 (handleMessage)。 - MessageQueue (消息队列):用来存放 Handler 发送过来的 Message。虽然叫队列,但它的内部实现是按时间 (
when) 排序的单向链表。 - Looper (循环器):是 MessageQueue 的管理者。它以无限循环的形式,不断地从 MessageQueue 中取出 Message,并交由其对应的 Handler 执行。
二、 核心流程源码解析
1. 引擎的启动:Looper.prepare() 与 Looper.loop()
在子线程中使用 Handler 之前,必须先调用 Looper.prepare(),然后调用 Looper.loop()。而在主线程中,系统(在 ActivityThread.main 中)已经自动为我们做好了这两步。
Looper.prepare():保证线程单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Looper.java
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");
}
// 将 Looper 存入 ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
// 创建与当前 Looper 绑定的 MessageQueue
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- ThreadLocal 的作用:
ThreadLocal保证了每个线程都有且只有一个自己的Looper实例。 - MessageQueue 的创建:在
Looper的构造函数中,同时创建了MessageQueue,因此一个线程 = 一个 Looper = 一个 MessageQueue。
Looper.loop():开启无限循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Looper.java
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;
// 死循环
for (;;) {
// 1. 从消息队列取出消息。这里可能会阻塞!
Message msg = queue.next();
if (msg == null) {
// 没有消息表示 MessageQueue 正在退出
return;
}
try {
// 2. 将消息分发给它的目标 (即发送它的 Handler)
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
throw exception;
}
// 3. 回收 Message,放入对象池复用
msg.recycleUnchecked();
}
}
Looper.loop() 的核心就是一个死循环,不断调用 queue.next() 取出消息,并通过 msg.target.dispatchMessage(msg) 分发消息。
面试题:主线程的死循环为什么不会导致 ANR? ANR 的本质是“处理某件特定事情超时”(例如按键事件 5 秒无响应),而不是“线程在等事情做”。 当 MessageQueue 中没有消息时,底层会通过 Linux 的
epoll机制将主线程挂起(休眠),此时线程会释放 CPU 资源,所以不会卡死。当有新消息或底层事件(如触摸屏幕)到来时,会唤醒主线程继续工作。真正的 ANR 是因为在dispatchMessage(即某次handleMessage) 中执行了耗时操作,导致后续的消息(如 UI 绘制事件)迟迟得不到处理。
2. 消息的发送与入队:enqueueMessage()
当我们调用 handler.sendMessage(msg) 时,经过层层调用,最终都会走到 MessageQueue.enqueueMessage()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (mQuitting) {
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 1. 如果队列为空,或者新消息的时间最早,将其插入到链表头部
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
// 如果线程正在阻塞,需要唤醒
needWake = mBlocked;
} else {
// 2. 否则,根据时间 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;
}
// 3. 唤醒底层的 epoll 等待
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
- 单链表排序:MessageQueue 内部维护的是一个单向链表
mMessages,新来的消息会根据它的执行时间 (when) 插入到链表的合适位置。 - 唤醒机制:如果队列原先为空导致线程正在休眠,插入新消息后会调用
nativeWake(mPtr)唤醒底层,让queue.next()恢复执行。
3. 消息的提取与阻塞:MessageQueue.next()
next() 是 MessageQueue 中最复杂的方法,它负责提供下一条需要执行的消息,如果没到时间,就让线程休眠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// MessageQueue.java
Message next() {
// ...
int nextPollTimeoutMillis = 0;
for (;;) {
// 1. Native 层阻塞操作 (基于 epoll)
// nextPollTimeoutMillis 为 -1 表示一直等,为 0 表示不等,>0 表示等几毫秒
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// ... (省略同步屏障逻辑)
if (msg != null) {
if (now < msg.when) {
// 2. 如果当前时间还没到第一条消息的执行时间,计算需要休眠的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 3. 拿到消息!将消息从链表中移除并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 4. 队列里没消息了,无限期等待 (-1)
nextPollTimeoutMillis = -1;
}
// ... (省略 IdleHandler 逻辑)
mBlocked = true;
}
}
}
4. 消息的分发:dispatchMessage()
在 Looper.loop() 中拿到消息后,会调用 msg.target.dispatchMessage(msg)。target 其实就是发送这条消息的 Handler 对象本身。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Handler.java
public void dispatchMessage(Message msg) {
// 1. 如果 Message 自身自带 callback (通过 handler.post(Runnable) 发送的)
if (msg.callback != null) {
handleCallback(msg); // 直接执行 msg.callback.run()
} else {
// 2. 如果 Handler 创建时传入了 mCallback (实现 Handler.Callback 接口)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; // 如果 Callback 返回 true,则拦截消息
}
}
// 3. 兜底,回调重写的 handleMessage() 方法
handleMessage(msg);
}
}
这里明确了消息处理的优先级:Message.callback > Handler.Callback > handleMessage()。
三、 进阶与常见面试题
1. Handler 内存泄漏的原因及解决方案
原因:在 Java 中,非静态内部类/匿名内部类会隐式地持有外部类的引用。如果我们在 Activity 中以匿名内部类的方式创建 Handler,并且往 MessageQueue 中发送了延迟消息 (postDelayed),那么: 主线程的 MessageQueue -> Message -> Handler -> Activity。 只要消息未被处理,Activity 就无法被垃圾回收,导致内存泄漏。
解决方案:
- 静态内部类 + 弱引用:将 Handler 写成静态内部类(不持有外部类引用),并在内部持有 Activity 的
WeakReference。 - 生命周期清理:在 Activity 的
onDestroy()中调用handler.removeCallbacksAndMessages(null)清空该 Handler 的所有消息。
2. Message 的复用 (享元模式)
为了避免频繁创建和销毁 Message 造成内存抖动,Message 内部维护了一个单链表的对象池 (sPool),最大容量为 50。
- 获取:使用
Message.obtain()而不是new Message()。它会从池子里取对象,如果池子空了才new。 - 回收:
Looper.loop()执行完dispatchMessage后,会调用msg.recycleUnchecked()将数据清空并放回池子头部。
3. 同步屏障 (Sync Barrier)
在 MessageQueue 中,有一类特殊的消息,它的 target == null,这就是同步屏障。 当调用 MessageQueue.postSyncBarrier() 时,会在队列中插入这样一个没有 target 的消息。 作用:当 MessageQueue.next() 遇到同步屏障时,它会跳过所有的普通消息(同步消息),只去寻找并执行异步消息(Asynchronous Message)。 应用场景:Android 的屏幕刷新 (VSYNC 信号)。ViewRootImpl 在触发 UI 绘制时,会发送一个同步屏障,并将 UI 绘制的任务作为异步消息发送。这样能保证 UI 绘制任务具有最高优先级,不被队列里的其他普通消息阻塞,保证界面的流畅。
4. IdleHandler
MessageQueue.IdleHandler 是一个回调接口,当消息队列空闲时(即队列无消息,或者消息都还没到执行时间准备休眠时)会触发它的 queueIdle() 方法。 应用场景:可以在这里执行一些不那么紧急的初始化操作,或者在 UI 绘制完成后做一些事情,从而加快 Activity 的启动速度,这就是常说的“延迟初始化”。
四、 总结
Android 的 Handler 机制是一套精妙的生产者-消费者模型:
Handler是生产者,负责将附带执行时间的Message塞入MessageQueue的对应位置。Looper运行在目标线程,是消费者,通过死循环不断在MessageQueue中取出到期的消息。MessageQueue借助底层 Linux 的epoll/eventfd机制实现了高效的“有事做事,无事休眠”,是整套机制的性能基石。