Handler 机制的终极 18 问,都了解了吗?建议收藏~
Posted TechMerger
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler 机制的终极 18 问,都了解了吗?建议收藏~相关的知识,希望对你有一定的参考价值。
我们经常提及 android 中极为重要的线程间通信方式即
Handler
机制,貌似 Handler 类发挥了很大的作用。
事实上当你了解它的原理之后,会发现 Handler 只是该机制的调用入口和回调而已,最重要的东西是 Looper
和 MessagQueue
,以及不断流转的 Message
。
本次针对该机制常被问及的 18 个问题进行整理和回答,供大家解惑和回顾~
文章目录
- 1. 简述下 Handler 的总体原理?
- 2. Looper 存在哪?如何保证线程独有?
- 3. Looper 缓存在 ThreadLocal 的作用是?
- 4. 主线程的 Main Looper 和普通 Looper 的异同?
- 5. Handler 或 Looper 如何切换线程?
- 6. Looper 的 loop() 为什么不卡死?
- 7. Looper 等待的时候线程到底是什么状态?
- 8. Looper 等待如何准确唤醒?
- 9. Message 如何获取?为什么?
- 10. MessageQueue 如何管理 Message?
- 11. 理解 Message 和 MessageQueue 的异同?
- 12. Handler、Mesage 和 Runnable 的关系如何理解?
- 13. IdleHandler 了解过吗?有什么用?
- 14. 异步 Message 或同步屏障了解过吗?怎么用?什么原理?
- 15. Looper、MessageQueue、Message 及 Handler 的关系?
- 16. Native 侧的 NativeMessageQueue 和 Looper 的作用是?
- 17. Native 侧如何使用 Looper?
- 18. 正确理解 Handler 导致的内存泄露?
1. 简述下 Handler 的总体原理?
- Looper#
prepare()
初始化线程独有的Looper
以及MessageQueue
- Looper#
loop()
开启死循环读取 MessageQueue 中下一个恰当 Message- 尚无 Message 的话,调用 Native 侧的
pollOnce()
进入无限等待 - Message 执行的
when
条件未满足的话,调用 pollOnce() 时传入超时参数进入有限等待
- 尚无 Message 的话,调用 Native 侧的
- 向持有 Looper 的
Handler
发送 Message 或Runnable
后 Message 将被插入到 Looper 持有的 MessageQueue 中合适的位置- MessageQueue 发现有合适的 Message 插入,调用 Native 侧的
wake()
唤醒线程,促使 MessageQueue 的读取进入下一次循环,因为此刻已有 Message 则出队和处理 - Native 侧有限等待后指线程将唤醒并继续读取 MessageQueue,因为时长条件将满足则将其出队处理
- MessageQueue 发现有合适的 Message 插入,调用 Native 侧的
- 直接回调 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 的异同?
-
区别:
1. Main Looper 不可
quit
主线程需要不断读取系统消息和用书输入,是进程的入口,只可被系统直接终止。进而其 Looper 在创建的时候设置了不可
quit
的标志,而其他线程的 Looper 则可以也必须手动 quit2. Main Looper 实例还被静态缓存
为了便于每个线程获得主线程 Looper 实例,见 Looper#getMainLooper(),Main Looper 实例还作为
sMainLooper
属性缓存到了 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 如何获取?为什么?
-
通过 Message 的静态方法
obatin()
获取,因为该方法不是无脑地 new,而是从单链表池子里获取实例,并在recycle()
后将其放回池子 -
可以复用 Message 实例,满足频繁使用 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 操作
- Activity
-
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 就不再可达了)
以上是关于Handler 机制的终极 18 问,都了解了吗?建议收藏~的主要内容,如果未能解决你的问题,请参考以下文章