android Handler , Looper , Message , MessageQueue 详解
在 Android 开发中,Handler、Looper、Message 和 MessageQueue 是构成线程间通信(尤其是子线程与主线程交互)的核心组件。它们共同组成了 Android 的消息驱动模型,确保 UI 操作在正确的线程执行,同时允许后台线程安全地更新界面。
1. 整体概览
-
Message:消息的载体,包含要传递的数据(如
what,arg1,arg2,obj,data)以及目标Handler。 -
MessageQueue:消息队列,采用单链表结构,按时间顺序存储
Message,提供入队和出队操作。 -
Looper:循环器,不断从
MessageQueue中取出消息,并分发给对应的Handler处理。每个线程最多只能有一个Looper。 -
Handler:消息的发送者和处理者,负责将
Message插入到MessageQueue,并在Looper所在的线程中执行handleMessage(Message)回调。
它们的关系可以概括为:Handler 发送 Message → MessageQueue 存储 Message → Looper 循环取出 Message → Handler 处理 Message。
┌─────────┐ 发送消息 ┌──────────────┐ 入队 ┌───────────────┐
│ Handler │ ──────────▶ │ MessageQueue │ ◀─────── │ Message │
└─────────┘ └──────────────┘ └───────────────┘
▲ │
│ │ next() 取出
│ ▼
│ ┌──────────────┐
└───────────────────│ Looper │ (循环)
dispatchMessage └──────────────┘
2. 各组件详细解析
2.1 Message(消息)
Message 是一个实现了 Parcelable 接口的 final 类,用于封装需要传递的数据。
主要字段:
| 字段 | 类型 | 说明 |
|---|---|---|
what | int | 用户自定义的消息标识,用于区分不同类型的消息 |
arg1 / arg2 | int | 轻量级的整型数据,避免创建对象 |
obj | Object | 任意对象,传递复杂数据 |
data | Bundle | 可传递更多的键值对数据 |
target | Handler | 目标 Handler,即最终处理该消息的 Handler |
callback | Runnable | 若设置,则优先于 handleMessage 执行 |
when | long | 消息应被处理的时间(基于 SystemClock.uptimeMillis()) |
next | Message | 指向队列中下一条消息,构成单链表 |
获取 Message 实例:
推荐使用 Message.obtain() 系列方法,它们从全局消息池中复用对象,避免频繁创建导致内存抖动。
Message msg = Message.obtain(); msg.what = 1; msg.obj = "data";
使用完后,在 handleMessage 中无需手动回收,Looper 会自动调用 recycle()。
2.2 MessageQueue(消息队列)
MessageQueue 是一个基于单链表的消息队列,按 when(触发时间)升序排列。它不是普通的 java.util.Queue,而是一个专门的实现,支持低层级的阻塞和唤醒。
关键方法:
-
boolean enqueueMessage(Message msg, long when):将消息插入队列,并根据when排序。若队列头部发生变化(如插入更早的消息),会通过 native 方法nativeWake()唤醒可能阻塞的next()。 -
Message next():取出下一条待处理的消息。若队列为空或队首消息的触发时间未到,则调用 native 方法nativePollOnce()进入阻塞等待,直到被唤醒或超时。 -
void quit(boolean safe):退出消息队列,释放 Looper 循环。safe=true时,会处理完所有未到期的消息;false则直接丢弃所有消息。
同步屏障(Sync Barrier):
MessageQueue 支持插入一种特殊消息(target == null)作为同步屏障,用于拦截同步消息,优先处理异步消息(如 UI 绘制、输入事件)。屏障通过 postSyncBarrier() 添加,移除需调用 removeSyncBarrier()。Handler 默认发送的是同步消息,若需异步消息,可在构造时传入 async=true(API 28+ 有 Handler.createAsync)。
2.3 Looper(循环器)
Looper 负责驱动消息循环,从 MessageQueue 中不断取出消息并分发。
核心方法:
-
static void prepare():为当前线程创建唯一的 Looper 实例,并存入ThreadLocal。 -
static void loop():启动消息循环。内部是一个for (;;)死循环,不断调用queue.next()获取消息,然后调用msg.target.dispatchMessage(msg)分发,最后回收消息。 -
static Looper myLooper():获取当前线程的 Looper。 -
static MessageQueue myQueue():获取当前线程的消息队列。 -
void quit()/quitSafely():结束循环。
主线程的 Looper:
主线程(ActivityThread)在 main() 方法中已经调用了 Looper.prepareMainLooper() 和 Looper.loop(),所以主线程自带消息循环,这也是为什么可以直接在主线程创建 Handler。
2.4 Handler(处理器)
Handler 是消息机制对外的接口,负责发送和处理消息。
构造方式:
-
无参构造:使用当前线程的 Looper(若不存在则抛异常)。
-
Handler(Looper looper):指定 Looper。 -
Handler(Callback callback):设置全局回调。 -
Handler(boolean async):是否发送异步消息。
发送消息的方法:
-
sendMessage(Message msg)/sendEmptyMessage(int what) -
sendMessageDelayed(Message msg, long delayMillis) -
sendMessageAtTime(Message msg, long uptimeMillis) -
post(Runnable r)/postDelayed(Runnable r, long delayMillis):将 Runnable 包装成 Message(callback字段),发送后由该 Handler 执行。
处理消息:
dispatchMessage(Message msg) 的执行优先级:
-
若
msg.callback != null,则调用msg.callback.run()(即 post 的 Runnable)。 -
若
Handler的mCallback != null,则调用mCallback.handleMessage(msg),若返回 true 则结束。 -
否则调用
Handler自身的handleMessage(msg)。
Handler 与线程的关系:
-
Handler 本身不创建新线程,它依赖 Looper 所在的线程执行消息处理。
-
一个线程可以有多个 Handler,但它们共享同一个 Looper 和 MessageQueue。
-
一个 Handler 只能绑定到一个 Looper,但可以向其他线程的 MessageQueue 发送消息(跨线程通信)。
3. 工作流程详解
以主线程更新 UI 为例:
-
创建 Handler:在主线程创建
Handler,它自动绑定主线程的 Looper。 -
子线程发送消息:在子线程中调用
handler.sendMessage(msg)。Handler 内部将msg.target设为自己,然后调用MessageQueue.enqueueMessage(msg, when)将消息放入主线程的消息队列。 -
主线程 Looper 循环:主线程的 Looper 在
loop()中不断调用queue.next()。若队列中有到期消息,则返回该消息;否则阻塞等待。 -
分发消息:Looper 拿到消息后,调用
msg.target.dispatchMessage(msg),即调用发送该消息的 Handler 的dispatchMessage。 -
处理消息:Handler 在
dispatchMessage中根据优先级调用handleMessage(msg)或Runnable.run(),此时代码运行在主线程,可以安全更新 UI。 -
消息回收:处理完毕后,
msg.recycle()将消息回收到消息池,供下次复用。
延时消息与阻塞唤醒:
-
发送延时消息时,
enqueueMessage会根据when插入适当位置。 -
Looper 的
next()发现队首消息尚未到期,会计算剩余时间并调用nativePollOnce(timeout)阻塞。 -
当有更早的消息入队时,会调用
nativeWake()唤醒next()重新检查队列。 -
超时后
next()自动返回消息,实现定时处理。
4. 使用场景与示例
4.1 主线程更新 UI
class MyActivity extends Activity {
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
textView.setText((String) msg.obj);
}
}
};
void doBackgroundTask() {
new Thread(() -> {
// 模拟耗时操作
String result = fetchData();
Message msg = mHandler.obtainMessage(1, result);
mHandler.sendMessage(msg);
}).start();
}
}
4.2 延时执行任务
handler.postDelayed(() -> {
// 3秒后执行
}, 3000);
// 取消延时任务
handler.removeCallbacks(runnable);
4.3 子线程间通信
class MyThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
Looper.loop();
}
}
4.4 定时任务(不推荐,可用 ScheduledExecutorService 或 AlarmManager)
handler.sendMessageDelayed(msg, 1000); // 一次性 // 循环发送:在 handleMessage 中再次 sendMessageDelayed
5. 常见问题与注意事项
5.1 内存泄漏
原因: 非静态内部类 Handler 持有外部 Activity 的引用,而 Message 持有 Handler 引用,如果消息尚未处理(如延时消息),则 Activity 无法被回收。
解决:
-
使用静态内部类 + 弱引用(WeakReference)持有 Activity。
-
在
onDestroy中调用handler.removeCallbacksAndMessages(null)清空消息队列。 -
使用
Handler.Callback接口避免继承 Handler。
5.2 子线程中直接创建 Handler 报错
错误信息:Can't create handler inside thread that has not called Looper.prepare()
原因: Handler 需要绑定一个 Looper,子线程默认没有 Looper。
解决: 先调用 Looper.prepare(),再创建 Handler,最后调用 Looper.loop() 启动循环。或者使用 Handler(Looper.getMainLooper()) 绑定主线程 Looper。
5.3 为什么主线程 Looper 死循环不会导致 ANR?
ANR 是指应用未响应输入事件或广播等,主线程 Looper 循环本身是正常的,它保证了事件的及时处理。当主线程执行耗时操作(如网络请求、大量计算)时,消息无法被及时处理,才导致 ANR。Looper 阻塞在 queue.next() 时处于休眠状态,不消耗 CPU,一旦有新消息到来会立即唤醒处理。
5.4 Message 复用与 Obtaining
使用 new Message() 会频繁创建对象,造成内存抖动。Message.obtain() 从全局消息池(链表)中取出一个 Message,其 recycle() 会清空字段并放回池中。注意:在 handleMessage 中不要手动调用 recycle(),因为 Looper 会在分发后自动回收;如果使用了 obtain,也不要将消息重复使用(如再次发送)。
5.5 异步消息与同步屏障
系统为了优先处理 UI 绘制、输入事件等,会插入同步屏障,此时只有异步消息能被处理。开发者通常不需要关心,但若需要在动画等场景获得更高优先级,可以使用异步 Handler:
Handler handler = Handler.createAsync(Looper.getMainLooper());
5.6 IdleHandler
MessageQueue 提供 IdleHandler 接口,当队列空闲(没有消息需要立即处理)时执行,适用于低优先级任务(如 GC、预加载)。
Looper.myQueue().addIdleHandler(() -> {
// 执行低优先级任务
return false; // 返回 true 表示继续保留,false 移除
});
6. 源码简要分析
-
Handler.enqueueMessage():调用
MessageQueue.enqueueMessage(msg, uptimeMillis),同时将msg.target = this。 -
MessageQueue.enqueueMessage():根据
when插入链表,若插入在头部则调用nativeWake()。 -
MessageQueue.next():无限循环,调用
nativePollOnce()阻塞,返回消息或继续等待。若遇到同步屏障,则只处理异步消息。 -
Looper.loop():调用
queue.next(),获取消息后执行msg.target.dispatchMessage(msg),然后msg.recycleUnchecked()。 -
Handler.dispatchMessage():依次检查
msg.callback、mCallback、handleMessage。
7. 总结
Android 的消息机制是一个高效、灵活的设计:
-
Handler 是发送和处理消息的接口。
-
MessageQueue 负责存储和管理消息,支持延时和屏障。
-
Looper 驱动消息循环,将消息分发给对应 Handler。
-
Message 是数据载体,通过对象池优化性能。
理解它们的工作原理有助于编写稳定、高效的 Android 应用,避免线程同步和内存泄漏等问题。尽管如今有 RxJava、协程等更高级的异步方案,但 Handler 机制依然是 Android 框架的基石,许多组件(如 Activity 生命周期、View 绘制、事件分发)都建立在它之上。
一、基础
Q1:请简述 Handler 机制的工作原理
核心回答:
Handler 发送消息 → MessageQueue 按时间排序存储 → Looper 无限循环取出 → 回调 Handler 处理。
细节:
-
MessageQueue不是队列,是单向链表,按when(触发时间戳)排序 -
Looper.loop()中queue.next()无消息时通过 native epoll 阻塞,不消耗 CPU -
最终
msg.target.dispatchMessage(msg),target就是发送该消息的 Handler
Q2:一个线程可以有几个 Handler?几个 Looper?
| 数量 | 原因 | |
|---|---|---|
| Looper | 1 个 | ThreadLocal 存储,prepare() 时检查重复,重复会抛异常 |
| Handler | 多个 | 多个 Handler 可以共享同一个 Looper 和 MessageQueue |
源码验证:
java
// Looper.prepare()
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
Q3:为什么主线程的 Looper.loop() 死循环不会导致 ANR?
核心要点:
ANR 的本质是消息处理超时,不是死循环本身。
深入解析:
-
loop()中的queue.next()无消息时,会通过 native 层的epoll_wait阻塞线程,释放 CPU -
有消息来时,native 层被唤醒,继续执行
-
ANR 发生在
dispatchMessage()里处理消息耗时过长(如 5s 内未响应输入) -
所以死循环 + 阻塞 = 事件驱动模型,这是高性能的
Q4:MessageQueue 的数据结构是什么?
答案:单向链表(按 when 时间戳排序)
java
// Message.java
Message next; // 链表指针
long when; // 触发时间
为什么不用数组/堆?
-
不需要随机访问,只取头部 → 链表 O(1)
-
插入按时间排序,数量通常不多,O(n) 可接受
-
内存更紧凑,且支持对象池复用
二、进阶深挖
Q5:Handler 的 sendMessageDelayed() 是怎么实现延迟的?
核心原理:
不是真的延迟发送,而是提前发送,按时间排队。
流程:
-
计算
when = SystemClock.uptimeMillis() + delayMillis -
enqueueMessage()按when插入链表合适位置 -
Looper循环到该消息时,检查when是否到达:-
未到:计算剩余时间,通过 native
nativePollOnce(ptr, timeout)阻塞等待 -
已到:取出处理
-
Q6:Handler 内存泄漏的原因?如何解决?
原因:
-
非静态内部类
Handler持有外部Activity的引用 -
延迟消息(如
postDelayed)在 Activity 销毁时仍在 MessageQueue 中 -
Message 持有 Handler,
Handler→Activity,导致 Activity 无法被回收
解决方案(3种):
java
// 方案1:静态内部类 + WeakReference
private static class SafeHandler extends Handler {
private WeakReference<Activity> ref;
SafeHandler(Activity activity) {
ref = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = ref.get();
if (activity != null && !activity.isFinishing()) {
// 处理消息
}
}
}
// 方案2:Activity.onDestroy() 时移除消息
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
// 方案3:使用 Lifecycle 组件自动管理
Q7:Message.obtain() 和 new Message() 有什么区别?
答案:
obtain()从消息对象池中复用,new会创建新对象。
源码:
java
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(); // 池空时才 new
}
好处: 避免频繁 GC,提升性能。Android 系统大量消息传递场景(如触摸事件、绘制)都用这个池。
Q8:post(Runnable) 和 sendMessage(Message) 有什么区别?
post(Runnable) | sendMessage(Message) | |
|---|---|---|
| 本质 | 把 Runnable 赋值给 msg.callback | 直接发送 Message |
| 处理优先级 | 高(dispatchMessage 先检查 callback) | 低 |
| 使用场景 | 简单任务 | 需要携带复杂数据 |
源码:
java
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;
}
Q9:IdleHandler 是什么?使用场景?
定义:
当 MessageQueue 空闲(无消息或消息未到触发时间)时执行的回调。
使用:
java
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 执行低优先级任务
return false; // true=保留,false=执行完移除
}
});
经典场景:
-
启动优化:主线程空闲时加载非关键资源
-
延迟初始化:如
LeakCanary的初始化 -
UI 收尾:如
RecyclerView预加载
Q10:什么是同步屏障(Sync Barrier)?
原理:
插入一个
target == null的特殊 Message,拦截其后所有同步消息,只允许异步消息通过。
使用场景:
-
ViewRootImpl在scheduleTraversals()时插入屏障 -
确保
Choreographer的 VSync 异步消息优先执行,避免掉帧
注意: 同步屏障是 @hide API,应用层不能直接调用。
三、实战场景题
Q11:如何在子线程中创建 Handler?
java
new Thread(() -> {
// 子线程默认没有 Looper,必须手动创建
Looper.prepare();
Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
// 在子线程处理
}
};
Looper.loop(); // 阻塞,开始消息循环
// 退出时(子线程可以退出)
// Looper.myLooper().quitSafely();
}).start();
更推荐使用 HandlerThread:
java
HandlerThread thread = new HandlerThread("bg");
thread.start();
Handler handler = new Handler(thread.getLooper());
Q12:Looper.quit() 和 quitSafely() 的区别?
quit() | quitSafely() | |
|---|---|---|
| 行为 | 立即退出,清空 MessageQueue | 设置退出标记,处理完已有消息后退出 |
| 延迟消息 | 直接丢弃 | 丢弃(只保证当前已到达的消息) |
| 使用 | 子线程 Looper | 子线程 Looper |
主线程 Looper 不允许 quit,会抛异常。
Q13:主线程的 Looper 是什么时候创建的?
答案: ActivityThread.main() 中。
java
// ActivityThread.java
public static void main(String[] args) {
Looper.prepareMainLooper(); // 创建主线程 Looper
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop(); // 开始循环,永不退出
}
Q14:为什么 Handler 构造方法里要求 Looper 不能为 null?
java
public Handler(Looper looper) {
this(looper, null, false);
}
原因:
-
Handler 必须关联一个 MessageQueue 才能发消息
-
MessageQueue 来自 Looper
-
如果当前线程没有调用
Looper.prepare(),Looper.myLooper()返回 null -
直接抛异常,避免 NPE 或消息丢失
Q15:触摸事件是怎么通过 Handler 机制传递到 View 的?
流程:
-
InputDispatcher(native)收到触摸事件 -
通过 JNI 将事件封装成
Message,发送到主线程的MessageQueue -
Looper取出消息,ViewRootImpl的WindowInputEventReceiver处理 -
最终分发到
DecorView→ViewGroup→View的dispatchTouchEvent()
这解释了为什么在主线程执行耗时操作会导致触摸无响应——消息被堵在队列里。
四、面试速记口诀
| 问题 | 一句话答案 |
|---|---|
| Handler 发消息去哪? | MessageQueue(链表) |
| Looper 干嘛的? | 死循环取消息,交给 Handler |
| 为什么不会卡死? | native epoll 阻塞,有消息才唤醒 |
| 延迟消息怎么实现? | 提前入队,按时间排序,到点才处理 |
| 内存泄漏怎么解? | 静态内部类 + WeakReference + removeCallbacks |
| 子线程用 Handler? | 先 Looper.prepare() + Looper.loop() |
更多推荐


所有评论(0)