本质

在指定线程中执行代码,能够持续响应系统内的消息并执行预置逻辑。

Android 中存在大量的消息驱动的方式的交互,理解 Handler 消息机制对阅读源码以及开发非常重要。

分层

Android 的消息机制分为两层

  • Java 层
  • Native 层

详细信息可参考袁辉辉的文章:

Android消息机制1-Handler(Java层)
Android消息机制2-Handler(Native层)

主要模型

  • Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
  • MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
  • Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
  • Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。

Message

它有一个 next 属性,这意味着消息以链表形式组织

target

when

分类

  • 普通消息(同步消息)
  • 异步消息
  • 屏障消息(同步屏障)

正常情况下消息按照 when 的先后顺序进行处理,但某些场景需要比保证重要的操作的消息(如 view 绘制)能够优先执行,因此将消息进行了优先级分类。

设置同步屏障后,会优先处理异步消息。

MessageQueue

enqueueMessage()

Handler 的所有发送消息的方法最终都会调用到该方法

Handler

dispatchMessage(Message msg)

👀【重要】安卓的消息机制 Handler - 图1

  1. msg 有 callback,交其处理
    2. Handler 有 callback,交其处理,如果该方法返回 false,3 可以被调用。否则不执行3
    3. 以上都没有,交由 自身 handleMessage() 处理

Looper

prepareMainLooper()

主线程 Looper 系统自动帮我们创建

prepare()

只能调用一次

loop()

死循环执行:
1. 读取 MessageQueue 中的下一条 Message
2. 将 Message 分发给 target 处理
3. 分发后的 Message 回收到 池里复用

ThreadLocal

Message Pool

解决复用问题,不需要每次都新建 message ,默认为 50 个

常见问题

IdleHandler

如果需要在消息队列空闲时执行某些逻辑,可以借助 IdleHandler 接口https://mp.weixin.qq.com/s/kpl8X9ZjOO_DewitoT7j-w

为什么一个线程只有一个 Looper 一个 MessageQueue

线程对应的 Looper 在 ThreadLocal 中存储。Looper 创建时的 Looper.prepare() 方法只能执行一次,从而保证了
一个线程只有一个 Looper,一个 MessageQueue

Loop 无限循环为什么不会卡死

子线程 Loop 无限循环,一旦任务结束,开发者应手动退出
主线程 Loop 无限循环,是为了处理程序运行过程中的各种消息,一旦退出循环意味着程序结束

主线程 Loop 为什么不会消耗大量 cpu 资源

当主线程 消息队列 空闲时,便阻塞在
loop 的 queue.next() 中 native 方法 nativePollOnce 中,此时主线程会释放 cpu 资源当有新消息到达时底层通过 epoll 机制唤醒主线程工作。故不会消耗大量 cpu 资源

MessageQueue 如何保证 消息顺序的,或者说 Delayed 是如何实现的

将 delayed 的时间转换为 message.when(绝对时间:当前时间 + delayed),messageQueue 插入消息时按照 when 进行插入

Handler 导致内存泄漏的原因和解决办法

活跃线程属于 GC Root,被 GC Root 直接持有或间接持有的引用不会被回收

推荐阅读 小题大做 | Handler内存泄露全面分析

🎧 垃圾回收 · 语雀

完整的引用连为:线程的 ThreadLocal 持有了该线程的 Looper,Looper 持有了它的 MessageQueue,MessageQueue 内持有了 Message,Message 持有了发送该消息的 Handler,而 Handler(可能)持有了 Activity 的引用。

解决方案是:

  • 关闭视图控制器时移除 Handler Callback
  • 使用静态内部类配合弱引用处理

同步屏障机制

https://blog.csdn.net/start_mao/article/details/98963744