10.01 Handler 消息机制原理
10.00 明确了异步编程的范式:工作线程干耗时活,切回主线程更新 UI。负责"切线程"的核心机制就是 Handler 消息机制——它是 Android 异步体系的地基:AsyncTask、HandlerThread、Activity 生命周期调度、ViewRootImpl 的重绘调度、图片库的线程切换,底层全是它。
本节把四大角色(Handler、Message、MessageQueue、Looper)与完整流转链路讲透。
# 一、整体架构:一个线程的"消息循环"
# 动态演示:消息是怎么流转的
下面这张动画图循环演示了消息的完整生命周期(橙点 = 消息在四段路径上流转;队列槽位变橙 = 有消息占用;绿点 = 消息处理完被回收进对象池):
对照动画分步理解(每一步对应后文的具体源码/小节):
| 动画阶段 | 发生了什么 | 对应实现 |
|---|---|---|
| ① 入队 | 工作线程调用 handler.sendMessage() / post(),消息带着 when 插入队列链表 | MessageQueue.enqueueMessage() |
| ② 出队 | 主线程 Looper.loop() 的 queue.next() 取到队头到期消息(没到期/没消息就休眠) | MessageQueue.next() + epoll |
| ③ 派发 | msg.target.dispatchMessage(msg) → 回到 Handler 绑定的线程(即主线程)执行 | Handler.dispatchMessage() |
| ④ 回收 | 处理完后 recycleUnchecked() 把 Message 放回对象池,obtain() 的消息复用它 | Message.recycleUnchecked() |
注意动画里的两个细节:多个消息错峰入队(队列同时可能挂多条,所以才会有"延迟消息被前面耗时消息堵住"的现象);绿点回到对象池后再次进入流水线(同一个 Message 对象被反复使用,这就是 obtain() 的意义)。
四大角色与职责:
| 角色 | 职责 | 关键说明 |
|---|---|---|
Message | 消息的载体 | what/arg1/arg2/obj 携带数据,when 决定执行时间,target 指向发送它的 Handler |
MessageQueue | 消息队列 | 按 when 排序的单链表(不是队列数组),支持插入定时消息、按时间取消息 |
Looper | 消息循环 | loop() 死循环不断从队列取消息,取到就 dispatchMessage 派发;取不到则阻塞休眠 |
Handler | 收发接口 | sendMessage/post 发消息入队;消息派发回来后在Handler 绑定的线程执行 |
"切线程"的原理一句话:Handler 在哪个线程创建(准确说:绑定了哪个线程的 Looper),消息就在哪个线程被处理。工作线程发消息 → 消息进入主线程的队列 → 主线程的 Looper 取出来 → 在主线程执行 handleMessage。
# 二、主线程的 Looper 从哪来
主线程的 Looper 由系统准备,入口在 ActivityThread.main():
// ActivityThread.main()(源码主干)
public static void main(String[] args) {
Looper.prepareMainLooper(); // ① 为主线程创建Looper
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop(); // ② 进入消息循环(永不返回)
}
// Looper 的关键源码(主干)
public static void prepareMainLooper() {
prepare(); // new Looper(true) 存入ThreadLocal
}
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) { // 死循环
Message msg = queue.next(); // 可能阻塞
if (msg == null) break; // quit时返回null
msg.target.dispatchMessage(msg); // 派发给Handler(target即发送者)
msg.recycleUnchecked(); // 回收进对象池
}
}
三个要点:
- 每个线程最多一个 Looper,存放在
ThreadLocal(线程本地存储,线程间隔离) Looper.loop()的死循环不占 CPU——queue.next()取不到消息时在 native 层通过 epoll 机制休眠(nativePollOnce),有新消息或定时消息到期时由nativeWake唤醒。这就是"主线程永远在死循环却不会卡死"的答案- 主线程 Looper 的死循环 ≠ ANR:ANR 是"某条消息(如输入事件、生命周期)没有在规定时间内被处理完",而不是循环本身。循环恰恰是保证消息能被逐条处理的前提(详见 10.03)
# 三、Message:消息与对象池复用
Message 推荐通过 Message.obtain() 获取而不是 new——它内部维护一个对象池(链表,默认上限 50 个),回收的消息被复用,避免高频发消息时的内存抖动(GC 压力,见 07.20 自定义View 的"避免在onDraw创建对象"同理):
// 消息的三种携带数据方式
Message msg1 = Message.obtain(handler, 1); // what=1
msg1.arg1 = 100; // 小整数用arg1/arg2
msg1.obj = resultObject; // 对象用obj(慎用跨进程场景)
handler.sendMessage(msg1);
// 更简洁:obtain的系列重载自动设置target
Message.obtain(handler, 1, 100, 0).sendToTarget();
// 携带Runnable(post方式,内部是msg.callback=runnable)
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("done"); // 在Handler绑定的线程执行
}
});
handler.post 与 sendMessage 的区别只是消息类型:post 的消息执行 msg.callback.run();sendMessage 的消息执行 handleMessage()。两者都走同一个队列。
# 四、Handler 的创建与子线程陷阱
Handler 创建时会绑定当前线程的 Looper:
// 主线程中:主Looper已由系统prepare,可直接创建
Handler mainHandler = new Handler();
// 子线程中:没有Looper,直接创建会崩溃
new Thread(new Runnable() {
@Override
public void run() {
Handler h = new Handler(); // 💥 Can't create handler inside thread
// that has not called Looper.prepare()
}
}).start();
// 子线程正确姿势:先prepare再loop
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 为当前线程创建Looper
Handler threadHandler = new Handler(); // 绑定子线程Looper
Looper.loop(); // 进入循环(阻塞在此)
// loop()之后的代码只在quit后执行
threadHandler.getLooper().quit(); // 退出循环(通常在destroy时调用)
}
}).start();
HandlerThread:官方封装的"带 Looper 的线程",省去手写 prepare/loop:
HandlerThread handlerThread = new HandlerThread("work-thread");
handlerThread.start();
// 串行后台任务队列:所有消息按序在该线程处理(相机操作、数据库串行写等场景常用)
Handler workHandler = new Handler(handlerThread.getLooper());
workHandler.post(new Runnable() {
@Override
public void run() {
// 在work-thread线程执行,可做耗时操作
String result = doHeavyWork();
// 切回主线程更新UI
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
textView.setText(result);
}
});
}
});
// 退出:不退出会导致线程泄漏
handlerThread.quitSafely();
跨线程切换的经典组合(上面的代码):后台 HandlerThread 处理耗时 → 主 Handler(Looper.getMainLooper())回 UI。这也是 AsyncTask、图片库内部的标准结构。
# 五、消息派发的分发优先级
dispatchMessage 的分发顺序(理解 post/send、Callback、handleMessage 三者关系的关键):
// Handler.dispatchMessage(源码主干)
public void dispatchMessage(Message msg) {
if (msg.callback != null) { // ① post()的消息:执行Runnable
handleCallback(msg); // msg.callback.run()
} else {
if (mCallback != null) { // ② Handler(Callback)构造:执行callback
if (mCallback.handleMessage(msg)) return;
}
handleMessage(msg); // ③ 兜底:子类重写的handleMessage
}
}
| 优先级 | 来源 | 触发方式 |
|---|---|---|
① msg.callback | handler.post(runnable) | 执行 Runnable |
② mCallback | new Handler(callback) | callback 返回 true 则拦截 |
③ handleMessage() | 继承 Handler 重写 | sendMessage 系列的常规路径 |
# 六、延迟消息与阻塞唤醒机制
# 6.1 postDelayed 的原理
postDelayed(runnable, 1000) 不是定时器,也不是延迟启动线程——消息立即入队,但 when = 当前开机时间 + 1000ms。队列按 when 升序排列,Looper 取消息时如果队头的 when 还没到,就休眠到到期时刻再取。所以:
- 延迟是近似的(受主线程前面消息处理时长影响,误差取决于队列繁忙度)
- 基于
SystemClock.uptimeMillis()(深度休眠不计入),适合 UI 调度而非精确计时
# 6.2 阻塞与唤醒:epoll 而非轮询
MessageQueue.next() 的核心调用链:
next() → 发现队头消息 when > 当前时间(或队列为空)
→ nativePollOnce(ptr, timeoutMillis) // native层epoll休眠timeout毫秒
← 新消息入队或到期 → nativeWake() // 唤醒
→ 返回该消息
同步屏障(sync barrier)(了解级别):postSyncBarrier 可以往队列插一个"屏障",使得队列只处理异步消息(setAsynchronous(true)),普通消息全部延后。系统用它保证 UI 绘制消息(Choreographer 的 VSYNC 调度)优先于普通消息,避免被大量任务消息挤兑——这就是屏幕绘制流畅的重要保障。应用层很少直接使用(API 隐藏)。
IdleHandler:队列空闲时(没有即刻要处理的消息)会回调 queue.addIdleHandler() 注册的任务,适合做"不着急"的初始化(启动优化中常用,见 10.03)。
# 七、典型陷阱与最佳实践
# 7.1 内存泄漏(最经典)
非静态内部类 Handler 隐式持有外部 Activity 引用;延迟消息在队列中存活期间,Message.target → Handler → Activity 这条链会让 Activity 无法回收:
public class DemoActivity extends AppCompatActivity {
// 💥 非静态内部类:持有Activity引用,延迟消息未处理完时Activity泄漏
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
textView.setText("done"); // 引用了Activity的textView
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
mHandler.sendEmptyMessageDelayed(1, 60_000); // 60秒后才处理,期间旋转屏销毁的Activity全泄漏
}
}
正确姿势(静态内部类 + 弱引用 + 及时清理):
public class DemoActivity extends AppCompatActivity {
// 静态内部类:不隐式持有Activity
private static class SafeHandler extends Handler {
private final WeakReference<DemoActivity> mActivityRef; // 弱引用持有
SafeHandler(DemoActivity activity) {
super(Looper.getMainLooper());
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
DemoActivity activity = mActivityRef.get();
if (activity == null || activity.isFinishing()) {
return; // Activity已销毁:丢弃消息,不更新UI
}
activity.textView.setText("done");
}
}
private final Handler mHandler = new SafeHandler(this);
@Override
protected void onDestroy() {
super.onDestroy();
// 清空该Handler的全部待处理消息
mHandler.removeCallbacksAndMessages(null);
}
}
# 7.2 其他要点
- 显式指定 Looper:
new Handler(Looper.getMainLooper())优于new Handler(),避免"在哪个线程创建就绑哪个线程"的隐式行为(Java 9+ / API 30 后构造函数要求显式传入 Looper) - 优先静态内部类 + HandlerThread/主 Looper,避免匿名
new Handler(){...} - 子线程 Handler 记得 quit:
HandlerThread用完quitSafely(),否则线程常驻泄漏 - Message 复用:用
Message.obtain(),不要 new;不要复用已在队列中的消息 - postDelayed 不做精确计时:UI 节奏用 Choreographer/属性动画,精确计时用
SystemClock+ 自己的比较逻辑
# 八、常见问题
- "主线程 Looper 死循环为什么不会卡死应用?"——循环会阻塞休眠(epoll),CPU 占用极低;应用没有消息要处理时主线程本来就该待机。真正要保证的是每条消息别处理太久(ANR 话题,见下一节)。
- "子线程能不能 new Handler?"——先
Looper.prepare()(或直接用 HandlerThread),否则崩溃。 - "为什么 Handler 构造传的是 Looper 而不是线程?"——消息循环的实体是 Looper(线程通过 ThreadLocal 持有它),Handler 绑定 Looper 即等于绑定线程。
- "sendMessage 和 post 哪个好?"——等价,post 少写 handleMessage 分发、适合简单回调;sendMessage 适合多种消息类型用 what 区分的场景。
# 结束
本节系统讲解了 Handler 消息机制:四大角色(Message / MessageQueue / Looper / Handler)的职责与协作、消息循环的完整流转(入队 → 取消息 → 派发 → 回收)、主线程 Looper 的系统初始化与"死循环不卡死"的 epoll 休眠原理、Message.obtain() 对象池复用、子线程创建 Handler 的陷阱与 HandlerThread、消息派发三级优先级、postDelayed 的定时实现与同步屏障(了解级),以及最重要的 Handler 内存泄漏(静态内部类 + 弱引用 + removeCallbacksAndMessages)。Handler 是 Android 异步的通用语言,下一节看它最常见的搭档——线程池:10.02 线程与线程池。更多内容请参考官方文档:与界面线程通信 (opens new window)。