跳到主要内容

Handler 机制源码

问题

Handler/Looper/MessageQueue 的底层是如何工作的?同步屏障是什么?

答案

核心类关系

Looper.loop() 核心逻辑

// 简化版 Looper.loop() 源码
public static void loop() {
final MessageQueue queue = myLooper().mQueue;
for (;;) {
// 从队列取消息(可能阻塞)
Message msg = queue.next();
if (msg == null) return; // quit 时返回 null

// 分发给对应的 Handler 处理
msg.target.dispatchMessage(msg);

// 回收消息到对象池
msg.recycleUnchecked();
}
}

MessageQueue.next() 阻塞原理

next() 方法内部调用 Linux 的 epoll 机制:

  • 队列为空或下一条消息未到执行时间 → nativePollOnce() 阻塞(不消耗 CPU)
  • 有新消息入队 → nativeWake() 唤醒 epoll
  • 这就是为什么主线程的 for(;;) 不会 ANR——空闲时线程是挂起的

同步屏障(Sync Barrier)

同步屏障用于优先处理异步消息(如 View 绘制消息):

// ViewRootImpl 中的使用
void scheduleTraversals() {
// 1. 插入同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 2. 发送异步消息(绘制回调)
mChoreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}

void doTraversal() {
// 3. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals(); // measure → layout → draw
}

这保证了 UI 绘制消息优先于其他消息被处理,避免掉帧。

Message 对象池

// Message.obtain() 从对象池获取,避免频繁创建
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;

public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
最佳实践

使用 Message.obtain()handler.obtainMessage() 获取 Message 实例,而不是 new Message()。对象池复用可以减少 GC 压力。


常见面试问题

Q1: 主线程的 Looper.loop() 死循环为什么不会 ANR?

答案

Looper.loop() 的死循环在没有消息时会通过 epoll_wait 进入休眠状态,不占用 CPU。ANR 是因为在主线程处理消息超时(Activity 5s、BroadcastReceiver 10s),而不是因为循环本身。整个 Android 应用的运行就是基于消息驱动的——用户点击、系统回调都是通过 Handler 发送消息到主线程的 MessageQueue。

Q2: Handler 的内存泄漏为什么会发生?

答案

匿名内部类 Handler 隐式持有外部 Activity 的引用。如果 Handler 发送了延迟消息(如 postDelayed),Message 持有 Handler(target 字段),Handler 持有 Activity。Activity 销毁后消息还在队列中,导致 Activity 无法被 GC。

解决方案:使用静态内部类 + WeakReference,或在 onDestroyhandler.removeCallbacksAndMessages(null)

相关链接