一、基础
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() |