Handler 机制的终极 18 问,都了解了吗?建议收藏~

Handler 机制的终极 18 问,都了解了吗?建议收藏~_第1张图片

我们经常提及 Android 中极为重要的线程间通信方式即 Handler 机制,貌似 Handler 类发挥了很大的作用。

事实上当你了解它的原理之后,会发现 Handler 只是该机制的调用入口和回调而已,最重要的东西是 LooperMessagQueue,以及不断流转的 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 的总体原理?

  1. Looper#prepare() 初始化线程独有的 Looper 以及 MessageQueue
  2. Looper#loop() 开启死循环读取 MessageQueue 中下一个恰当 Message
    • 尚无 Message 的话,调用 Native 侧的 pollOnce() 进入无限等待
    • Message 执行的 when 条件未满足的话,调用 pollOnce() 时传入超时参数进入有限等待
  3. 向持有 Looper 的 Handler 发送 Message 或 Runnable 后 Message 将被插入到 Looper 持有的 MessageQueue 中合适的位置
    • MessageQueue 发现有合适的 Message 插入,调用 Native 侧的 wake() 唤醒线程,促使 MessageQueue 的读取进入下一次循环,因为此刻已有 Message 则出队和处理
    • Native 侧有限等待后指线程将唤醒并继续读取 MessageQueue,因为时长条件将满足则将其出队处理
  4. 直接回调 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 则可以也必须手动 quit

    2. Main Looper 实例还被静态缓存

    为了便于每个线程获得主线程 Looper 实例,见 Looper#getMainLooper(),Main Looper 实例还作为 sMainLooper 属性缓存到了 Looper 类中。

  • 相同点:

  1. 都是通过 Looper#prepare() 间接调用 Looper 构造函数创建的实例

  2. 都缓存到了静态实例 ThreadLocal 中方便每个线程获取自己的 Looper 实例

5. Handler 或 Looper 如何切换线程?

  1. Handler 创建的时候指定了其所属线程的 Looper,同时持有了 Looper 独有的 MessageQueue

  2. Looper的loop() 会持续读取 MessageQueue 中合适的 Message,没有 Message 的时候进入等待

  3. 当向 Handler 发送 Message 或 post Runnable 后,Handler 会向持有的 MessageQueue 中插入 Message

  4. Message 抵达并满足条件后会唤醒 MessageQueue 所属的线程,并将 Message 返回给 Looper

  5. 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 了解过吗?有什么用?

  • 适用于期望空闲时候执行,但不影响主线程操作的任务

  • 系统应用:

    1. Activity destroy 回调就放在了 IdleHandler
    2. ActivityThreadGCHandler 使用了 IdleHandler,在空闲的时候执行 GC 操作
  • App 应用:

    1. 发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个 View
    2. 将某部分初始化放在 IdleHandler 里不影响 Activity 的启动。。。
  • 关于 IdleHandler 的其他问题:

    1. add/remove IdleHandler 的方法,是否需要成对使用?

    不需要,回调返回 false 也可以移除

    1. mIdleHanders 一直不为空时,为什么不会进入死循环?

    执行过 IdleHandler 之后会将计数重置为 0,确保下一次循环不重复执行

    1. 是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?

    最好不要,回调时机不太可控,需要搭配 remove 谨慎使用

    1. 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,进行后续的 waitwake,后续将创建 wake 的FD,并通过 epoll 机制等待或唤醒。但并不参与管理 Java 的 Message

  • Native 侧也需要使用 Looper 机制,等待和唤醒的需求是同样的,所以将这部分实现都封装到了 Looper.cpp 中,供 Java 和 Native 一起使用

17. Native 侧如何使用 Looper?

  • Looper Native 部分承担了 Java 侧 Looper 的等待和唤醒,除此之外其还提供了 Message、MessageHandlerWeakMessageHandlerLooperCallbackSimpleLooperCallback 等 API

  • 这些部分可供 Looper 被 Native 侧直接调用,比如 InputFlinger 广泛使用

  • 主要方法是调用 Looper 构造函数或 prepare 创建 Looper,然后通过 poll 开始轮询,接着 sendMessageaddEventFd,等待 Looper 的唤醒。使用过程和 Java 的调用思路类似

18. 正确理解 Handler 导致的内存泄露?

  • 持有 Activity 实例的内名内部类或内部类的生命周期应当和 Activity 保持一致
  • 如果 Activity 本该销毁了,但异步任务仍然活跃或通过 Handler 发送的 Message 尚未处理完毕,将使得内部类实例的生命周期被错误地延长
  • 造成本该回收的 Activity 实例被别的 ThreadMain Looper 占据而无法及时回收(活跃的 Thread 或 静态属性 sMainLooper 是 GC Root 对象)
  • 记得持有 Activity 尽量采用静态内部类 + 弱引用的写法,另外在 Activity 销毁的时候及时地终止 Thread 或清空 Message(Message 清空后会执行 recycle(),内部将重置 target 等属性,handler 就不再可达了)

你可能感兴趣的