handler机制原理面试总结
1. 定义与作用 (核心目的):
Handler 是 Android 中用于线程间通信的一种机制,主要用于解决后台线程执行耗时操作后需要更新 UI 的问题。因为 Android 规定 UI 操作必须在主线程(UI 线程)中进行,而耗时操作(如网络请求、文件读写)应在子线程执行。Handler 提供了一种安全的方式让子线程将消息或任务发送到主线程去执行,从而更新 UI。
2. 核心组件及其关系:
Handler: 消息的发送者和处理者。它负责:- 将
Message或Runnable对象发送到关联的MessageQueue中。 - 在关联的
Looper从MessageQueue取出消息后,处理该消息(执行其handleMessage方法或Runnable的run方法)。
- 将
Message: 消息的载体。可以携带简单的数据(如int、String、Object)或表示一个任务。MessageQueue: 一个先进先出(FIFO)的消息队列。它存储由Handler发送过来的Message或Runnable。每个线程最多只能有一个MessageQueue。Looper: 消息循环器。它不断地从关联的MessageQueue中取出Message或Runnable,并将其分派给对应的Handler处理。每个线程最多只能有一个Looper。- 关系:
- 一个
Looper绑定一个线程(通常是主线程)和一个MessageQueue。 - 一个
Handler绑定一个Looper(从而也绑定了该Looper所在的线程和MessageQueue)。 Handler发送Message到绑定的Looper的MessageQueue中。Looper循环地从MessageQueue取消息,并调用目标Handler的handleMessage方法来处理。
- 一个
3. 工作原理流程:
- 初始化: 在目标线程(通常是主线程)中,系统会自动创建
Looper和MessageQueue(主线程不需要手动创建Looper)。在子线程中,需要手动调用Looper.prepare()创建Looper和MessageQueue,然后调用Looper.loop()启动消息循环。 - 创建 Handler: 在目标线程中创建
Handler对象(例如在主线程的Activity或Fragment中创建)。这个Handler会自动关联到该线程的Looper和MessageQueue。 - 发送消息: 在子线程中:
- 可以通过
Handler的sendMessage(Message msg)、sendMessageDelayed、post(Runnable r)、postDelayed等方法发送消息或任务到关联的MessageQueue。 - 这些方法内部会将消息放入目标线程的
MessageQueue中排队。
- 可以通过
- 消息循环: 目标线程的
Looper持续运行(loop()方法),不断检查MessageQueue:- 如果队列为空,
Looper会阻塞(进入休眠状态)。 - 如果队列非空,
Looper取出队头的消息。
- 如果队列为空,
- 消息处理:
Looper将取出的消息分派给创建该消息的Handler(即msg.target)。Handler调用其handleMessage(Message msg)方法(处理Message)或在目标线程执行Runnable的run()方法(处理Runnable)。这一步发生在目标线程(通常是主线程),因此可以安全地更新 UI。
4. 关键点强调:
- 线程关联性:
Handler必须在其关联线程(即创建它的线程)中处理消息。这是保证 UI 操作在主线程执行的关键。 - 避免内存泄漏: 在
Activity或Fragment中使用Handler时,如果Handler持有对Activity的隐式引用(如匿名内部类),而消息队列中还有该Handler的消息,会导致Activity无法被回收,造成内存泄漏。解决方案:- 使用静态内部类
Handler+ 弱引用WeakReference持有Activity。 - 在
Activity的onDestroy()中移除所有待处理的消息:handler.removeCallbacksAndMessages(null)。
- 使用静态内部类
- 同步屏障与异步消息:默认情况下所有消息都是同步的。同步屏障是一种特殊的消息(
target为null),它的作用是阻止其后所有的同步消息,只允许异步消息(通过setAsynchronous(true)标记)被处理。这为高优先级任务(如UI绘制、VSYNC信号)开辟了“快速通道” - looper.loop()为何不卡死主线程:
Looper.loop()内部是一个死循环,但主线程并未卡死。关键在于,当消息队列为空时,Looper会通过Linux的epoll机制在Native层阻塞,释放CPU资源。当有新消息入队时,它会被唤醒并继续工作。ANR的发生并非因为循环本身,而是因为某个消息的处理时间过长(如耗时操作放在主线程),导致系统无法响应其他用户输入。 - 应用场景: 除了子线程更新 UI,
Handler还常用于执行延时任务 (postDelayed)、消息调度等。
5. 简洁总结:
Handler 机制是 Android 异步处理的核心之一。它通过 Handler(发送和处理)、Message(消息载体)、MessageQueue(消息队列)和 Looper(消息循环)四个组件的协作,实现了跨线程(主要是子线程到主线程)的安全通信,确保耗时操作后的 UI 更新在主线程安全执行。理解其内部流程和避免内存泄漏是使用好 Handler 的关键。
6.面试遇到不会的点
(1)哪些是同步消息,哪些是异步消息?
我当时理解错了,应该按照这个思路走
| 消息类型 | 特征 | 创建方式 | 处理优先级 | 典型应用场景 |
|---|---|---|---|---|
| 同步消息 |
| 通过默认Handler的 | 正常按时间顺序处理,可被屏障消息阻塞 | 绝大部分常规的线程间通信,如子线程通知主线程更新UI |
| 异步消息 |
| 1. 调用 2. 使用异步Handler(构造函数参数 async为true)发送消息 | 当存在屏障消息时,会优先于同步消息被处理 | 高优先级的任务,如屏幕垂直同步(VSync)信号触发的UI绘制 |
| 屏障消息 |
| 通过 | 本身不被处理,作用是屏蔽后续的同步消息,只允许异步消息通过 | 为异步消息创建优先处理环境,通常成对使用(添加后需移除) |
(2)handler怎么处理postDelayed延迟消息
-
消息的封装与发送
当你调用
handler.postDelayed(runnable, delayMillis)时,Handler会首先将Runnable对象封装成一个Message,并将其callback字段指向你的Runnable。接着,它会计算消息的目标执行时间点(when),这个时间点是系统开机到当前的时间(SystemClock.uptimeMillis())加上你设定的延迟时间。然后,这个消息会被送入与当前线程关联的MessageQueue中。 -
消息的有序入队
MessageQueue内部维护了一个按
when字段升序排列的单链表。新的消息到来时,并不会简单地加到队尾,而是会遍历链表,找到第一个执行时间比自己晚的消息,然后插入到它之前,从而保证队列总是按执行时间排序的。如果新消息需要被插入到链表头部,并且此时Looper正因处理延迟消息而阻塞,则会通过nativeWake方法唤醒线程,以便重新检查消息队列。 -
消息的取出与执行
在Looper的
loop()循环中,会不断调用MessageQueue.next()方法尝试获取下一条消息。该方法会检查队列头部的消息:-
如果还没有到执行时间,它会计算需要等待的时间差,然后调用
nativePollOnce(ptr, timeoutMillis)方法,让线程进入指定时间的阻塞等待状态,从而释放CPU资源 -
一旦等待超时(时间到了),或者有新的、更紧急的消息(如执行时间更早的消息)入队并唤醒了线程,
next方法就会返回这条消息。Looper随后将消息分发给对应的Handler,最终执行你提交的Runnable任务
-
(3)handler的message消息队列使用的链表是哪种
MessageQueue(消息队列)内部是使用一个单链表来存储和管理 Message对象的
为何使用单链表?
选择单链表作为底层数据结构,主要是基于消息队列的核心操作需求:
-
高效的插入操作:消息队列最主要的操作之一是插入新消息(
enqueueMessage)。由于消息是按执行时间排序的,新消息需要插入到链表中合适的位置。单链表在中间插入或删除节点时效率很高,只需要修改指针的指向,而不需要像数组那样进行大量的数据移动 -
灵活的排序需求:
MessageQueue需要根据延迟时间(postDelayed)等因素对消息进行排序。单链表结构可以灵活地支持这种按时间顺序的插入
