Android Handler 机制源码分析
一、整体结构
csharp
复制代码
Handler 机制
├── Handler - 发送消息、处理消息
├── Message - 消息载体
├── MessageQueue - 消息队列(单链表,按 when 排序)
├── Looper - 循环取消息并分发
└── ThreadLocal - 每个线程持有自己的 Looper
二、Looper.prepare():初始化 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");
}
sThreadLocal.set(new Looper(quitAllowed));
}
步骤说明
步骤
逻辑
说明
原因
1
sThreadLocal.get() != null
检查当前线程是否已有 Looper
避免同一线程重复 prepare,导致多个 Looper 竞争同一 MessageQueue,引发消息错乱
2
抛异常
一个线程只能有一个 Looper
保证线程与 Looper 一一对应,消息路由清晰
3
new Looper(quitAllowed)
创建 Looper,内部会创建 MessageQueue
Looper 需要持有自己的消息队列,用于存储待处理消息
4
sThreadLocal.set(looper)
将 Looper 存入当前线程的 ThreadLocal
线程隔离,每个线程只能访问自己的 Looper,实现多线程各自的消息循环
三、Looper 构造函数
java
复制代码
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); // 创建消息队列
mThread = Thread.currentThread(); // 绑定当前线程
}
步骤说明
步骤
逻辑
说明
原因
1
new MessageQueue(quitAllowed)
创建该 Looper 的消息队列
每个 Looper 需要独立队列存储消息,quitAllowed 控制主线程 Looper 是否允许退出
2
mThread = Thread.currentThread()
记录 Looper 所在线程
用于校验 Handler 是否在正确线程处理消息,以及 debug 时定位线程
四、Looper.loop():消息循环
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;
// 步骤1:获取当前线程的 Looper 和 MessageQueue
for (;;) {
Message msg = queue.next(); // 步骤2:阻塞取消息
if (msg == null) {
return; // 队列退出时返回
}
// 步骤3:分发消息
msg.target.dispatchMessage(msg);
msg.recycleUnchecked(); // 步骤4:回收 Message 到池
}
}
步骤说明
步骤
逻辑
说明
原因
1
myLooper()、me.mQueue
获取当前线程的 Looper 和 MessageQueue
通过 ThreadLocal 取本线程 Looper,保证 loop 操作的是本线程的队列
2
queue.next()
阻塞取下一个消息,无消息时线程休眠
有消息时立即处理,无消息时休眠避免空转耗 CPU,由 enqueueMessage 的 nativeWake 唤醒
3
msg.target.dispatchMessage(msg)
调用 Handler 的 dispatchMessage 处理消息
target 即发送该消息的 Handler,保证消息由正确的 Handler 处理
4
msg.recycleUnchecked()
将 Message 回收到对象池
减少 GC,Message 使用频繁,复用可显著降低内存分配和回收开销
五、MessageQueue.next():取消息
java
复制代码
Message next() {
final long ptr = mPtr;
if (ptr == 0) return null; // native 层已销毁
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
// 步骤1:native 阻塞,nextPollTimeoutMillis 为 0 不阻塞,-1 永久阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prev = null;
Message msg = mMessages; // 链表头
if (msg != null && msg.target == null) {
// 步骤2:遇到屏障消息,跳过普通消息,只处理异步消息
do {
prev = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 步骤3:未到执行时间,计算等待时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 步骤4:到时间了,取出消息
mBlocked = false;
if (prev != null) prev.next = msg.next;
else mMessages = msg.next;
msg.next = null;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1; // 无消息,永久阻塞
}
// ... IdleHandler 处理
}
}
}
步骤说明
步骤
逻辑
说明
原因
1
nativePollOnce(ptr, nextPollTimeoutMillis)
在 native 层阻塞,0 不阻塞,-1 永久阻塞
使用 epoll 等机制在 native 层休眠,比 Java 层 wait 更高效,且可与 native 事件(如输入、绘制)统一调度
2
msg.target == null
消息屏障,跳过普通消息,只处理异步消息
用于 View 绘制等需要优先执行的场景,屏障期间普通消息暂不处理,保证 UI 流畅
3
now < msg.when
未到执行时间,计算需要等待的毫秒数
支持延时消息,避免过早执行,nextPollTimeoutMillis 让 native 层精确唤醒
4
取出 msg、更新链表
从链表移除该消息并返回
消息已被消费,需从队列移除,防止重复处理
5
nextPollTimeoutMillis = -1
无消息时永久阻塞
队列空时线程休眠,等新消息入队时由 nativeWake 唤醒,避免忙等
六、Handler 发送消息:sendMessage / post
java
复制代码
// post(Runnable) 本质是发送一个 callback 类型的 Message
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r; // Runnable 存在 callback 字段
return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) return false;
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 绑定 Handler,用于 loop 时回调
return queue.enqueueMessage(msg, uptimeMillis);
}
步骤说明
步骤
逻辑
说明
原因
1
post(Runnable)
封装成 Message,callback = r
统一用 Message 表示任务,Runnable 作为 callback 在 dispatchMessage 时执行
2
Message.obtain()
从对象池取 Message
避免频繁 new Message,降低 GC 压力
3
sendMessageDelayed
计算 uptimeMillis = now + delayMillis
将相对延时转为绝对时间戳,便于队列按时间排序
4
msg.target = this
指定处理该消息的 Handler
loop 时通过 target 找到发送者,将消息分发给正确的 Handler
5
queue.enqueueMessage()
按 when 插入 MessageQueue
保证延时消息和普通消息按时间顺序执行
七、MessageQueue.enqueueMessage():入队
java
复制代码
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) throw new IllegalArgumentException("Message must have a target.");
if (msg.isInUse()) throw new IllegalStateException("msg already in use");
synchronized (this) {
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 = false;
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); // 步骤3:唤醒 native 层
}
}
return true;
}
步骤说明
步骤
逻辑
说明
原因
1
`p == null
when < p.when`
2
遍历链表
按 when 升序找到插入位置并插入
保证消息按时间顺序执行,延时消息在正确时机被取出
3
nativeWake(mPtr)
若在阻塞,唤醒 native 层
next() 在 nativePollOnce 中休眠,新消息入队后必须唤醒才能及时处理,否则会一直阻塞
八、Handler.dispatchMessage():分发消息
java
复制代码
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 步骤1:post(Runnable) 的情况,直接执行 callback
handleCallback(msg);
} else {
if (mCallback != null) {
// 步骤2:Handler 设置了 Callback,先交给它处理
if (mCallback.handleMessage(msg)) return;
}
// 步骤3:最终交给 handleMessage
handleMessage(msg);
}
}
private static void handleCallback(Message msg) {
msg.callback.run(); // 执行 Runnable
}
步骤说明
步骤
逻辑
说明
原因
1
msg.callback != null
post(Runnable) 发送的,执行 msg.callback.run()
post 时把 Runnable 存到 callback,此处直接执行,无需再建 Message 子类
2
mCallback != null
若 Handler 有 Callback,先由 Callback 处理
支持外部注入处理逻辑,若返回 true 表示已处理,不再调用 handleMessage
3
handleMessage(msg)
子类重写的方法,处理业务逻辑
作为默认处理入口,子类重写以实现具体业务
九、Message 对象池
java
复制代码
// Message 复用,避免频繁 new
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0;
sPoolSize--;
return m;
}
}
return new Message();
}
void recycleUnchecked() {
flags = FLAG_IN_USE;
next = sPool;
sPool = this;
sPoolSize++;
}
步骤说明
步骤
逻辑
说明
原因
obtain
从 sPool 取头节点
有可用 Message 则复用
主线程消息量大,复用可减少 GC,提升性能
recycleUnchecked
将当前 Message 放回 sPool 头
用完后回收到池
与 obtain 配合形成复用链,避免频繁创建和销毁
十、整体流程图
scss
复制代码
主线程启动
│
├─ Looper.prepare() ──→ 创建 Looper、MessageQueue,存入 ThreadLocal
│
└─ Looper.loop() ──→ 死循环
│
└─ queue.next() ──→ 阻塞取消息
│
└─ msg.target.dispatchMessage(msg) ──→ Handler 处理
Handler.post/sendMessage
│
├─ Message.obtain() ──→ 从池取 Message
├─ msg.target = this
├─ msg.callback = runnable (post 时)
│
└─ queue.enqueueMessage() ──→ 按 when 插入队列
│
└─ nativeWake() ──→ 若在阻塞则唤醒
十一、小结
组件
作用
Looper
绑定线程,持有 MessageQueue,loop() 循环取消息并分发
MessageQueue
单链表按 when 排序,native 层实现阻塞/唤醒
Handler
发送消息、处理消息,绑定到创建它的 Looper
Message
消息载体,含 target、callback、when,支持对象池复用