本质
在指定线程中执行代码,能够持续响应系统内的消息并执行预置逻辑。
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
target
when
分类
- 普通消息(同步消息)
- 异步消息
- 屏障消息(同步屏障)
正常情况下消息按照 when 的先后顺序进行处理,但某些场景需要比保证重要的操作的消息(如 view 绘制)能够优先执行,因此将消息进行了优先级分类。
设置同步屏障后,会优先处理异步消息。
MessageQueue
enqueueMessage()
Handler 的所有发送消息的方法最终都会调用到该方法
Handler
dispatchMessage(Message msg)
- 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 直接持有或间接持有的引用不会被回收
完整的引用连为:线程的 ThreadLocal 持有了该线程的 Looper,Looper 持有了它的 MessageQueue,MessageQueue 内持有了 Message,Message 持有了发送该消息的 Handler,而 Handler(可能)持有了 Activity 的引用。
解决方案是:
- 关闭视图控制器时移除 Handler Callback
- 使用静态内部类配合弱引用处理