我们经常提及 Android 中极为重要的线程间通信方式即 Handler 机制,貌似 Handler 类发挥了很大的作用。
事实上当你了解它的原理之后,会发现 Handler 只是该机制的调用入口和回调而已,最重要的东西是 Looper 和 MessagQueue ,以及不断流转的 Message 。
本次针对该机制常被问及的 18 个问题进行整理和回答,供大家解惑和回顾~
1. 简述下 Handler 的总体原理?
- Looper#
prepare() 初始化线程独有的 Looper 以及 MessageQueue - Looper#
loop() 开启死循环读取 MessageQueue 中下一个恰当 Message
- 尚无 Message 的话,调用 Native 侧的
pollOnce() 进入无限等待 - Message 执行的
when 条件未满足的话,调用 pollOnce() 时传入超时参数进入有限等待 - 向持有 Looper 的
Handler 发送 Message 或 Runnable 后 Message 将被插入到 Looper 持有的 MessageQueue 中合适的位置
- MessageQueue 发现有合适的 Message 插入,调用 Native 侧的
wake() 唤醒线程,促使 MessageQueue 的读取进入下一次循环,因为此刻已有 Message 则出队和处理 - Native 侧有限等待后指线程将唤醒并继续读取 MessageQueue,因为时长条件将满足则将其出队处理
- 直接回调 Message 的
callback 属性即 Runnable,或依据 target 属性即 Handler 回调 handleMessage()
2. Looper 存在哪?如何保证线程独有?
- Looper 实例被缓存在静态属性
sThreadLocal 中 ThreadLocal 内部通过 Map 和弱引用的方式缓存了每个线程独有的 Looper,所以无论在哪个线程调用 myLooper() 都可以从 ThreadLocal 中获取其对应的 Looper 实例
3. Looper 缓存在 ThreadLocal 的作用是?
- 并非不是用来切换线程的,只是为了让每个线程方便程获取自己的 Looper 实例,见 Looper#
myLooper() - 后续可供 Handler 初始化时指定其所属的 Looper 线程
- 或用来判断是否是主线程
4. 主线程的 Main Looper 和普通 Looper 的异同?
-
都是通过 Looper#prepare() 间接调用 Looper 构造函数创建的实例 -
都缓存到了静态实例 ThreadLocal 中方便每个线程获取自己的 Looper 实例
5. Handler 或 Looper 如何切换线程?
-
Handler 创建的时候指定了其所属线程的 Looper,同时持有了 Looper 独有的 MessageQueue -
Looper的loop() 会持续读取 MessageQueue 中合适的 Message,没有 Message 的时候进入等待 -
当向 Handler 发送 Message 或 post Runnable 后,Handler 会向持有的 MessageQueue 中插入 Message -
Message 抵达并满足条件后会唤醒 MessageQueue 所属的线程,并将 Message 返回给 Looper -
Looper 接着回调 Message 所指向的 Handler Callback 或 Runnable,达到线程切换的目的
简言之,向 Handler 发送 Message 其实是向 Handler 所属的线程独有 MessageQueue 插入 Message。而线程独有的 Looper 又会持续读取 MessageQueue 和唤醒。
所以发送完 Message 之后,将切换到其所属的线程并运行。
6. Looper 的 loop() 为什么不卡死?
为了让主线程持续处理用户的输入,loop() 是死循环,并不断调用 MessageQueue#next() 读取合适的 Message。
但当没有 Message 的时候,会调用 pollOnce() 并通过 Linux 的 epoll 机制进入休眠并释放资源。同时 eventFd 会监听 Message 抵达的写入事件并进行唤醒。
这样可以达到实时接收输入,适时释放资源且不卡死线程的目的。
7. Looper 等待的时候线程到底是什么状态?
调用 Linux 的 epoll 机制进入等待,事实上 Java 线程处于 Runnable 状态。
8. Looper 等待如何准确唤醒?
读取合适 Message 的 MessageQueue#next() 会因为 Message 尚无或执行条件尚未满足进行两种等的等待:
-
无限等待 尚无 Message(队列中没有 Message 或建立了同步屏障但尚无异步 Message)的时候,调用 Natvie 侧的 `pollOnce()V 会传入参数 -1。 Linux 执行 epoll_wait() 将进入无限等待,其等待合适的 Message 插入后调用 Native 侧的 wake() 向唤醒 fd 写入事件触发唤醒 -
有限等待 有限等待的场合将下一个 Message 剩余时长作为参数交给 epoll_wait(),epoll 将等待一段时间之后自动返回,接着回到 MessageQueue 读取的下一次循环
9. Message 如何获取?为什么?
注意:缓存池存在上限 50,没必要无限制地缓存,本身也是一种浪费
10. MessageQueue 如何管理 Message?
- MessageQueue 通过单链表管理 Message,不同于进程复用的 Message Pool,其是线程独有的
- 通过 Message 的执行时刻 when 对 Message 进行排队和出队
11. 理解 Message 和 MessageQueue 的异同?
-
相同点: 都是通过单链表来管理 Message 实例; Message 通过 obtain() 和 recycle() 向单链表获取插入节点,MessageQueue 通过 enqueueMessage() 和 next() 向单链表获取和插入节点 -
区别: Message 单链表是静态的,供进程使用的缓存池; MessageQueue 单链表非静态,只供 Looper 线程使用;
12. Handler、Mesage 和 Runnable 的关系如何理解?
-
作为使用 Handler 机制的入口,Handler 是发送 Message 或 Runnable 的起点 -
发送的 Runnable 本质上也是 Message,只不过作为 callback 属性被持有 -
Handler 持有 MesageQueue,最终 Message 实例都被插入到队列中,等待调度 -
Handler 作为 target 属性被持有在 Mesage 中,在 Message 执行条件满足的时候供 Looper 回调
事实上,Handler 只是供 App 使用 Handler 机制的 API,实质来说,Message 是更为重要的载体。
13. IdleHandler 了解过吗?有什么用?
-
适用于期望空闲时候执行,但不影响主线程操作的任务 -
系统应用:
- Activity
destroy 回调就放在了 IdleHandler 中 ActivityThread 中 GCHandler 使用了 IdleHandler,在空闲的时候执行 GC 操作 -
App 应用:
- 发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个 View
- 将某部分初始化放在 IdleHandler 里不影响 Activity 的启动。。。
-
关于 IdleHandler 的其他问题:
add/remove IdleHandler 的方法,是否需要成对使用? 不需要,回调返回 false 也可以移除
- 当
mIdleHanders 一直不为空时,为什么不会进入死循环? 执行过 IdleHandler 之后会将计数重置为 0,确保下一次循环不重复执行
- 是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?
最好不要,回调时机不太可控,需要搭配 remove 谨慎使用
- IdleHandle 的
queueIdle() 运行在那个线程? 取决于 IdleHandler add 到的 MessageQueue 所处的线程
14. 异步 Message 或同步屏障了解过吗?怎么用?什么原理?
-
异步 Message 是设置了 isAsync 属性的 Message 实例,可以用异步 Handler 发送,也可以调用 Message#setAsynchronous() 直接设置为异步 Message -
同步屏障指的是在 MessageQueue 的某个位置放一个没有 target 属性的 Message,确保此后的非异步 Message 无法执行,只能执行异步 Message -
原理: 当 MessageQueue 轮循 Message 时候发现建立了同步屏障的时候,会去跳过其他 Message,读取下个 async 的 Message 并执行,屏障移除之前同步 Message 都会被阻塞 -
应用: 比如屏幕刷新 Choreographer 就使用到了同步屏障,确保屏幕刷新事件不会因为队列负荷影响屏幕及时刷新。 -
注意: 同步屏障的添加或移除 API 并未对外公开,App 需要使用的话需要依赖反射机制
15. Looper、MessageQueue、Message 及 Handler 的关系?
- Looper 负责轮循 MessageQueue,保持线程不结束
- MessagQueue 负责管理待处理 Message 的入队和出队
- Message 是承载任务的载体,由 MessageQueue 进行调度
- Handler 则是对外公开的 API,负责发送 Message 和处理任务的回调
16. Native 侧的 NativeMessageQueue 和 Looper 的作用是?
-
NativeMessageQueue 负责连接 Java 侧的 MessageQueue,进行后续的 wait 和 wake ,后续将创建 wake 的FD,并通过 epoll 机制等待或唤醒。但并不参与管理 Java 的 Message -
Native 侧也需要使用 Looper 机制,等待和唤醒的需求是同样的,所以将这部分实现都封装到了 Looper.cpp 中,供 Java 和 Native 一起使用
17. Native 侧如何使用 Looper?
-
Looper Native 部分承担了 Java 侧 Looper 的等待和唤醒,除此之外其还提供了 Message、MessageHandler 或 WeakMessageHandler 、LooperCallback 或 SimpleLooperCallback 等 API -
这些部分可供 Looper 被 Native 侧直接调用,比如 InputFlinger 广泛使用 -
主要方法是调用 Looper 构造函数或 prepare 创建 Looper,然后通过 poll 开始轮询,接着 sendMessage 或 addEventFd ,等待 Looper 的唤醒。使用过程和 Java 的调用思路类似
18. 正确理解 Handler 导致的内存泄露?
- 持有 Activity 实例的内名内部类或内部类的生命周期应当和 Activity 保持一致
- 如果 Activity 本该销毁了,但异步任务仍然活跃或通过 Handler 发送的 Message 尚未处理完毕,将使得内部类实例的生命周期被错误地延长
- 造成本该回收的 Activity 实例被别的
Thread 或 Main Looper 占据而无法及时回收(活跃的 Thread 或 静态属性 sMainLooper 是 GC Root 对象) - 记得持有 Activity 尽量采用静态内部类 + 弱引用的写法,另外在 Activity 销毁的时候及时地终止 Thread 或清空 Message(Message 清空后会执行 recycle(),内部将重置 target 等属性,handler 就不再可达了)
|