Android应用启动后,主线程会进入一个循环,这个循环不会退出,持续处理各种事件。点击、滑动、Activity生命周期回调、View绘制请求,最终都会转换成一条条消息放进队列里等待执行。这套机制的核心就是Handler、Looper、MessageQueue。很多人每天都在用Handler更新UI,却很少深究消息到底是怎么从发送端到达处理端的。理解其内部实现,能帮助你避开ANR、内存泄漏等常见问题。

三者的职责与协作模型
MessageQueue是一个按时间优先级排列的单链表结构,只负责存储消息,不负责处理。每条Message对象内部有when字段表示期望执行时间,队列按when升序排列。当一个消息插入时,enqueueMessage会遍历队列找到合适位置,如果新消息时间最早,就唤醒线程。这种设计避免了线程频繁轮询带来的CPU消耗。
Looper是每个线程的消息泵。它的loop方法里有一个死循环,不断调用MessageQueue.next取消息,取到后交给消息携带的Handler分发。没有消息时next会阻塞,线程休眠;有更早的消息到达时通过native层唤醒。主线程就是因为启动时调用了Looper.prepareMainLooper和Looper.loop,才能持续响应任务而不退出。
Handler是消息的发送者和处理者。它持有Looper和MessageQueue的引用,可以往队列里插入消息。每个Message对象有个target字段记录发送它的Handler,Looper取出消息后会调用msg.target.dispatchMessage(msg),最终回调到Handler的handleMessage或Runnable的run方法。这三个类就像邮局、邮递员和包裹的关系。
从源码看消息入队与分发
MessageQueue.enqueueMessage的逻辑比较清晰:如果队列为空,或者新消息的when小于头部消息的when,就插入头部并唤醒;否则向后遍历找到第一个when大于新消息的节点,插到它前面。如果消息带有同步屏障标记,处理方式略有不同。所有入队操作都通过synchronized保证线程安全,因为多个线程可能同时向主线程队列发消息。
Looper.loop的核心伪代码如下:先调用myLooper获取当前线程Looper,再拿到对应队列,然后进入for(;;)循环。循环里调用queue.next取出消息,如果返回null说明队列已退出,循环结束。取到消息后调用msg.target.dispatchMessage(msg),并在finally里回收消息。dispatchMessage内部先检查msg.callback,再检查Handler的mCallback,最后才走到handleMessage。这个顺序决定了post的Runnable优先级高于重写的handleMessage。
下面是一个在主线程使用Handler的典型示例,注意不要持有Activity的强引用:
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
// 更新界面
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.sendEmptyMessage(1);
}
}
子线程默认没有Looper,直接创建Handler会抛异常。如果需要后台线程处理消息,必须先Looper.prepare,处理完再Looper.loop开启循环,退出时调用Looper.quit或quitSafely。下面的代码演示了一个带消息循环的工作线程:
class WorkerThread extends Thread {
public Handler handler;
@Override
public void run() {
Looper.prepare();
handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
// 在后台线程处理耗时任务
}
};
Looper.loop();
}
public void quit() {
Looper.myLooper().quitSafely();
}
}
延时消息与同步屏障机制
延时消息并没有单独的定时器线程,它依赖MessageQueue.next的阻塞超时。next方法里如果头部消息的when大于当前时间,会计算差值作为nativePollOnce的等待时间。休眠期间如果发生新的更早消息插入,native层会唤醒。这样既保证了准确度又避免了空转。sendMessageDelayed和postDelayed最终都会设置msg.when,本质相同。
同步屏障是Android为提高UI响应引入的特殊机制。在MessageQueue中插入一个target为null的消息,它会成为一个屏障,next方法遇到屏障时会跳过所有同步消息,只取出异步消息。ViewRootImpl在请求布局时就会向主线程队列插入同步屏障,然后发送异步消息执行遍历绘制,确保绘制任务不被普通消息延迟。开发者可以通过调用MessageQueue.postSyncBarrier和设置Message.setAsynchronous为true来使用,但这两个API是隐藏的,需要通过反射调用。
下面展示同步屏障的代码思路,实际使用时需要反射或系统权限:
MessageQueue queue = Looper.myQueue(); Message barrier = Message.obtain(); barrier.target = null; queue.postSyncBarrier(barrier); Message asyncMsg = Message.obtain(); asyncMsg.setAsynchronous(true); handler.sendMessage(asyncMsg); queue.removeSyncBarrier(barrier);
常见问题与最佳实践
Handler内存泄漏的根源是Message持有Handler引用,Handler又持有Activity引用,而Message可能长时间未处理,导致Activity无法回收。解决思路是使用静态内部类加WeakReference,在onDestroy里调用removeCallbacksAndMessages清空队列。另外,非静态内部类Handler会隐式持有外部类实例,编译器也会给出警告。
IdleHandler是主线程空闲时的回调,适合做低优先级任务。当MessageQueue.next没有更多消息可处理时,会遍历IdleHandler列表并执行。返回true表示保留继续监听,返回false表示移除。可以用它来做启动优化,在首帧绘制完成后预加载数据。注意回调不能执行耗时操作,否则会拖慢下一次消息处理。
退出Looper时,quit会直接清空队列并退出,而quitSafely会处理完所有非延时消息再退出。主线程Looper不允许退出,否则应用就会崩溃或黑屏。在后台线程使用完Looper后务必调用quit,否则线程会一直阻塞在loop中无法回收。掌握这些细节才能写出稳定高效的消息驱动代码。
HandlerLooperMessageQueue修改时间:2026-09-25 21:43:55