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,或在 onDestroy 中 handler.removeCallbacksAndMessages(null)。