handler机制原理面试总结

1. 定义与作用 (核心目的):

Handler 是 Android 中用于线程间通信的一种机制,主要用于解决后台线程执行耗时操作后需要更新 UI 的问题。因为 Android 规定 UI 操作必须在主线程(UI 线程)中进行,而耗时操作(如网络请求、文件读写)应在子线程执行。Handler 提供了一种安全的方式让子线程将消息或任务发送到主线程去执行,从而更新 UI。

2. 核心组件及其关系:

  • Handler 消息的发送者和处理者。它负责:
    • MessageRunnable 对象发送到关联的 MessageQueue 中。
    • 在关联的 LooperMessageQueue 取出消息后,处理该消息(执行其 handleMessage 方法或 Runnablerun 方法)。
  • Message 消息的载体。可以携带简单的数据(如 intStringObject)或表示一个任务。
  • MessageQueue 一个先进先出(FIFO)的消息队列。它存储由 Handler 发送过来的 MessageRunnable。每个线程最多只能有一个 MessageQueue
  • Looper 消息循环器。它不断地从关联的 MessageQueue 中取出 MessageRunnable,并将其分派给对应的 Handler 处理。每个线程最多只能有一个 Looper
  • 关系:
    • 一个 Looper 绑定一个线程(通常是主线程)和一个 MessageQueue
    • 一个 Handler 绑定一个 Looper(从而也绑定了该 Looper 所在的线程和 MessageQueue)。
    • Handler 发送 Message 到绑定的 LooperMessageQueue 中。
    • Looper 循环地从 MessageQueue 取消息,并调用目标 HandlerhandleMessage 方法来处理。

3. 工作原理流程:

  1. 初始化: 在目标线程(通常是主线程)中,系统会自动创建 LooperMessageQueue(主线程不需要手动创建 Looper)。在子线程中,需要手动调用 Looper.prepare() 创建 LooperMessageQueue,然后调用 Looper.loop() 启动消息循环。
  2. 创建 Handler: 在目标线程中创建 Handler 对象(例如在主线程的 ActivityFragment 中创建)。这个 Handler 会自动关联到该线程的 LooperMessageQueue
  3. 发送消息: 在子线程中:
    • 可以通过 HandlersendMessage(Message msg)sendMessageDelayedpost(Runnable r)postDelayed 等方法发送消息或任务到关联的 MessageQueue
    • 这些方法内部会将消息放入目标线程的 MessageQueue 中排队。
  4. 消息循环: 目标线程的 Looper 持续运行(loop() 方法),不断检查 MessageQueue
    • 如果队列为空,Looper 会阻塞(进入休眠状态)。
    • 如果队列非空,Looper 取出队头的消息。
  5. 消息处理: Looper 将取出的消息分派给创建该消息的 Handler(即 msg.target)。Handler 调用其 handleMessage(Message msg) 方法(处理 Message)或在目标线程执行 Runnablerun() 方法(处理 Runnable)。这一步发生在目标线程(通常是主线程),因此可以安全地更新 UI。

4. 关键点强调:

  • 线程关联性: Handler 必须在其关联线程(即创建它的线程)中处理消息。这是保证 UI 操作在主线程执行的关键。
  • 避免内存泄漏:ActivityFragment 中使用 Handler 时,如果 Handler 持有对 Activity 的隐式引用(如匿名内部类),而消息队列中还有该 Handler 的消息,会导致 Activity 无法被回收,造成内存泄漏。解决方案:
    • 使用静态内部类 Handler + 弱引用 WeakReference 持有 Activity
    • ActivityonDestroy() 中移除所有待处理的消息:handler.removeCallbacksAndMessages(null)
  • 同步屏障与异步消息 :默认情况下所有消息都是同步的。同步屏障 是一种特殊的消息(targetnull),它的作用是阻止其后所有的同步消息,只允许异步消息 (通过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)哪些是同步消息,哪些是异步消息?

我当时理解错了,应该按照这个思路走

消息类型 特征 创建方式 处理优先级 典型应用场景
同步消息 msg.target不为null,指向发送它的Handler 通过默认Handler的post(Runnable)sendMessage()系列方法发送 正常按时间顺序处理,可被屏障消息阻塞 绝大部分常规的线程间通信,如子线程通知主线程更新UI
异步消息 msg.target不为null,且被标记了 FLAG_ASYNCHRONOUS标志 1. 调用Message.setAsynchronous(true) 2. 使用异步Handler(构造函数参数asynctrue)发送消息 当存在屏障消息时,会优先于同步消息被处理 高优先级的任务,如屏幕垂直同步(VSync)信号触发的UI绘制
屏障消息 msg.target为null 通过MessageQueue.postSyncBarrier()方法插入 本身不被处理,作用是屏蔽后续的同步消息,只允许异步消息通过 为异步消息创建优先处理环境,通常成对使用(添加后需移除)

(2)handler怎么处理postDelayed延迟消息

  1. 消息的封装与发送

    当你调用handler.postDelayed(runnable, delayMillis)时,Handler会首先将Runnable对象封装成一个Message,并将其callback字段指向你的Runnable。接着,它会计算消息的目标执行时间点(when),这个时间点是**系统开机到当前的时间(SystemClock.uptimeMillis())加上你设定的延迟时间。**然后,这个消息会被送入与当前线程关联的MessageQueue中。

  2. 消息的有序入队

    MessageQueue内部维护了一个按when字段升序排列的单链表 。新的消息到来时,并不会简单地加到队尾,而是会遍历链表,找到第一个执行时间比自己晚的消息,然后插入到它之前 ,从而保证队列总是按执行时间排序的。如果新消息需要被插入到链表头部,并且此时Looper正因处理延迟消息而阻塞,则会通过nativeWake方法唤醒线程,以便重新检查消息队列。

  3. 消息的取出与执行

    在Looper的loop()循环中,会不断调用MessageQueue.next()方法尝试获取下一条消息。该方法会检查队列头部的消息:

    • 如果还没有到执行时间 ,它会计算需要等待的时间差,然后调用nativePollOnce(ptr, timeoutMillis)方法,让线程进入指定时间的阻塞等待状态,从而释放CPU资源

    • 一旦等待超时 (时间到了),或者有新的、更紧急的消息 (如执行时间更早的消息)入队并唤醒了线程,next方法就会返回这条消息。Looper随后将消息分发给对应的Handler,最终执行你提交的Runnable任务

(3)handler的message消息队列使用的链表是哪种

MessageQueue(消息队列)内部是使用一个单链表来存储和管理 Message对象的

为何使用单链表?

选择单链表作为底层数据结构,主要是基于消息队列的核心操作需求:

  • 高效的插入操作 :消息队列最主要的操作之一是插入新消息(enqueueMessage)。由于消息是按执行时间排序的,新消息需要插入到链表中合适的位置。单链表在中间插入或删除节点时效率很高,只需要修改指针的指向,而不需要像数组那样进行大量的数据移动

  • 灵活的排序需求MessageQueue需要根据延迟时间(postDelayed)等因素对消息进行排序。单链表结构可以灵活地支持这种按时间顺序的插入

相关推荐
LYFlied21 分钟前
【每日算法】 LeetCode 56. 合并区间
前端·算法·leetcode·面试·职场和发展
利剑 -~29 分钟前
mysql面试题整理
android·数据库·mysql
梁同学与Android3 小时前
Android ---【经验篇】ArrayList vs CopyOnWriteArrayList 核心区别,怎么选择?
android·java·开发语言
沐怡旸3 小时前
【翻译】adb screenrecord 帮助文档
android
yaoh.wang4 小时前
力扣(LeetCode) 100: 相同的树 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
lienyin4 小时前
Android 简单的SFTP服务端+客户端通信传文件
android
周星星日记4 小时前
vue中hash模式和history模式的区别
前端·面试
小丑7554 小时前
Vue 转盘抽奖 transform
面试
Java水解7 小时前
MySQL索引分析以及相关面试题
后端·mysql·面试
掘金安东尼7 小时前
顶层元素问题:popover vs. dialog
前端·javascript·面试