1. Handler消息机制
a. 核心流程
-
发送消息(生产者线程)
- 线程A 调用
handler.sendMessage(msg)或post(Runnable)。 - Handler 将消息插入到与当前线程关联的 MessageQueue 中(通过
enqueueMessage())。
- 线程A 调用
-
消息队列管理
- MessageQueue 是一个基于时间优先级的单链表结构,按
when(触发时间)排序。 - 如果消息是延时的(
sendMessageDelayed),会计算出绝对时间再插入队列。
- MessageQueue 是一个基于时间优先级的单链表结构,按
-
消息循环(消费者线程)
- 线程B 的 Looper 通过
loop()方法无限循环调用MessageQueue.next()。 next()可能会阻塞(如队列为空或队首消息未到执行时间),通过 Linux 的epoll机制释放 CPU 资源。
- 线程B 的 Looper 通过
-
消息分发与处理
-
Looper 从队列取出消息后,调用
handler.dispatchMessage(msg)。 -
Handler 根据消息类型决定执行:
- 若消息有
Runnable,则执行Runnable.run()。 - 否则调用
handleMessage(msg)(需子类重写)。
- 若消息有
-
b. 关键组件
(1)Message
-
消息的载体,包含:
public int what; // 消息标识 public int arg1, arg2; // 简单数据 public Object obj; // 复杂对象 public long when; // 触发时间(毫秒) Handler target; // 目标Handler Runnable callback; // 回调任务 -
优化建议:通过
Message.obtain()或handler.obtainMessage()复用消息对象(减少内存分配)。
(2)MessageQueue
-
数据结构:单链表(按
when排序)。 -
核心方法:
enqueueMessage():插入消息并唤醒队列(如果被阻塞)。next():取出消息,可能阻塞(通过nativePollOnce())。
(3)Looper
-
作用:驱动消息循环。
-
关键代码:
public static void loop() { for (;;) { Message msg = queue.next(); // 可能阻塞 if (msg == null) return; // 唯一退出条件 msg.target.dispatchMessage(msg); } } -
线程绑定:通过
ThreadLocal保证每个线程有独立的 Looper。
(4)Handler
-
绑定关系:创建时关联当前线程的 Looper。
-
消息分发逻辑:
public void dispatchMessage(Message msg) { if (msg.callback != null) { msg.callback.run(); // 处理Runnable } else if (mCallback != null) { mCallback.handleMessage(msg); // 回调接口 } else { handleMessage(msg); // 子类重写 } }
c. 阻塞与唤醒机制
-
阻塞:
- 队列为空时,
next()调用nativePollOnce()进入无限期阻塞。 - 队首消息未到执行时间,阻塞至指定时间。
- 队列为空时,
-
唤醒:
- 当新消息插入队列且位于队首时,调用
nativeWake()唤醒 Looper
- 当新消息插入队列且位于队首时,调用
d. 同步屏障机制
-
设置同步屏障:
postSyncBarrier()- 插入一个特殊token到消息队列
- 会阻塞所有同步消息的处理
-
异步消息:
- 设置
Message.setAsynchronous(true) - 不受同步屏障影响
- 设置
-
移除同步屏障:
removeSyncBarrier(token) -
应用场景:ViewRootImpl在绘制UI时使用同步屏障确保绘制消息优先处理
e. IDLEHandler机制
IdleHandler允许在消息队列空闲时执行任务:
// 消息队列为空时,才会获取idleHandler的数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 只有在消息队列为空,且没有idlehandler时,才会阻塞,否则会执行idlehandler
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
特点:
- 当消息队列没有立即要处理的消息时触发
- 可用于执行低优先级的后台任务
2. 常见面试题
a. Handler 引起的内存泄露原因以及最佳解决方案
- 原因:延迟消息,持有了强引用如activity,如果activity销毁了,就会泄漏activity
- 解决方案:持有弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。
b. Handler 里藏着的 Callback 能干什么
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg); // msg自带的callback优先级最高,对应handler.post中的runnable
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) { // 拦截消息
return;
}
}
handleMessage(msg);
}
}
c. Handler中post和send的区别
-
post底层也是调用的sendMessageDelayed
-
post是send自带runnable的send:
- 省去了创建消息的过程
- 可以快速执行消息,且无法被handler拦截
d. 创建 Message 实例的最佳方式
为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗:
- 通过 Message 的静态方法 Message.obtain();
- 通过 Handler 的公有方法 handler.obtainMessage()
e. 为何不使用 Java 中的 wait/notify 来实现阻塞等待呢?
1. 设计目标不同
-
wait()/notify()- 是 Java 原生的 线程间低级同步机制,主要用于 锁竞争条件下的线程阻塞/唤醒。
- 需要手动管理
synchronized块和锁对象,容易出错(如死锁、虚假唤醒)。
-
Handler- 是 Android 的 消息驱动机制,专为 线程间通信 设计,尤其适合 主线程与子线程的交互。
- 内部封装了
MessageQueue和Looper,自动处理消息排队、分发和线程切换。
2. Handler 的优势
(1) 避免死锁风险
wait()/notify()必须配合synchronized使用,若锁未正确释放或唤醒顺序错误,容易导致死锁。Handler通过 无锁队列(MessageQueue)实现消息传递,无需显式同步。
(2) 支持延时和异步
Handler可以轻松发送 延时消息(postDelayed())或 异步任务(post()),而wait()只能被动等待唤醒。
(3) 天然适配主线程(UI线程)
- Android 的主线程默认有一个
Looper,通过Handler可以安全地更新 UI(子线程通过Handler将任务抛到主线程执行)。 wait()/notify()若在主线程误用,会导致 ANR(主线程阻塞)。
(4) 更灵活的消息处理
Handler可以区分不同类型的消息(通过msg.what),并统一在handleMessage()中处理。wait()/notify()只能通过全局状态变量判断,代码难以维护。
f. View.post 和 Handler.post的区别
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action); // 1. 本质上仍然调用handler.post
}
// 2. 区别是如果View还没有attach,则会先存起来
getRunQueue().post(action);
return true;
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
// 3. attach的时候,执行这些暂存的action
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}