聊一聊Android的Handler

作者: 2c3d4f7ba0d4 | 来源:发表于2019-07-24 16:11 被阅读4次

    Handler对于Android开发者而言,应该是再熟悉不过的了。每个Handler在创建时,就会绑定于一个特定线程内(创建时所在的线程),同时也绑定于该线程所在的Message Queue。

    Handler是做什么的?

    归类下来,其实是两个用处:

    1. 管理该线程内未来任务的执行次序。(schedule功能)
    2. 不同线程之间的消息通信。(包括UI线程和非UI线程之间的通信,或者两个非UI线程之间的通信)

    第一个作用,我们一般是通过 post 系列方法(post, postDelayed, postAtTime)或者 sendMessage 系列方法(sendMessage, sendMessageAtTime, sendMessageDelayed, sendEmptyMessage)实现。 post系列方法是传递的一个Message,send系列方法是传递的一个Message。

    Handler和Looper到底是什么关系?

    Handler的 schedule 任务功能是通过 Looper 和 MessageQueue 实现的。Looper 可以开启一个消息循环,不停的处理进入到该循环内的消息。这些消息是通过一个消息队列(MessageQueue)来存储管理的。

    UI线程默认就开启了消息循环(系统的 MainLooper),其他线程默认没有开启,可以通过Looper的 prepare() 和 loop() 方法进行开启。

    因此,Handler 和 Looper 的关系其实和 Thread 分不开的。每一个 Handler 在创建时会依附于它所在的thread,而每一个thread 也都有自己的一个 looper。对于用户(开发者)而言,looper 的消息循环功能主要是通过 handler 来进行调用的。

    每个线程都有自己的一个Looper吗?

    是的,每个线程都有自己的一个 looper,主线程( UI 线程)的 looper 是 MainLooper。

    为什么主线程的Looper循环不会造成ANR?

    看过一些 Android 源码的同学应该知道,有一个 ActivityThread 类,其中有个 main() 方法,这个是整个app程序的入口。而在 main() 方法里,会开启一个 Looper 的 loop()方法(也即是我们平时说的主线程的 MainLooper)。

    前面我们有提到,loop() 其实就是一个循环,不停的去查询消息。可能有同学会有疑问,既然它是主线程里的一个死循环,那为什么它不会造成 ANR 呢?

    回答这个问题,我们首先要理解 ANR 的本质:ANR 是主线程里的操作太耗时时(一般是超过5s),Android 弹出的一个提示。而我们这里说的 loop 循环,是不会引起这种情况的。looper 使用了 linux 上的 epoll 机制,在没有新消息过来时,线程会被挂起,让出 CPU 资源而进入休眠状态。其实主线程大多数时候是处于休眠状态的,并不会大量占用CPU资源。

    简单讲,其他线程可能会源源不断的向主线程发送消息,这些消息都会在主线程的 MessageQueue 里。主线程的 looper 会去轮询,从 MessageQueue 里取出消息,分发给主线程的 handler 去执行(参考 ActivityThread 的内部类 H)。在队列里没有消息时,主线程会被挂起,让出 CPU 资源(这是 linux 的 epoll 机制)。在有新的消息过来时,主线程又会被唤醒继续起来工作。

    也即是,ANR 是主线程里的某些操作太耗时,导致不能响应用户的交互。而 looper 的循环是在有需要时去取出消息供执行,不需要时即休眠,loop 的循环操作本身是不会影响 ANR 的。

    为什么 Handler 使用不当可能会造成内存泄露?

    Handler 造成内存泄露最常见的场景是,在 activity 里直接初始化了一个非静态类的 Handler 实例,这种情况下,当 activity 需要销毁时,如果主线程的消息队列中还存在没有处理完的 Message,就会造成内存泄露。

    主要的原因其实有下面几点:

      1. 非静态的内部类会持有外部类的引用。如果在 activity 里直接 new 了一个 Handler,那么这个 handler 实例就会持有这个 activity 实例的引用。
      1. 每一个 Message 都会持有一个对应 Handler 的实例。(参考 Android Message.java 源码,可以看到每个 Message 里都有一个名为 target 的成员变量,这个 target 就是对应的 Handler) 当在 UI 线程中创建一个 Handler 时,这个 Handler 就会和 UI 线程中的消息队列进行关联,每一个发送到 UI 线程消息队列中的消息都会持有这个 Handler 的引用。
      1. 主线程的 Looper 生命周期是跟随 App 的,而不是某一个 Activity。因此当 activity 实例待销毁时,如果有 Message 没有处理完,Message 是会继续存在于 MainLooper所属的 MessageQueue 中的。

    综上,在 activity 实例待销毁时,如果这个 activity 里存在持有 activity 引用的 Handler,并且消息队列里还有消息未处理完,就可能造成 activity 不能被回收。

    规避方式:

    最稳妥的方式是不要使用非静态内部类,并且如果需要持有 activity 的引用的话,要通过弱应用的方式。

    相关文章

      网友评论

        本文标题:聊一聊Android的Handler

        本文链接:https://www.haomeiwen.com/subject/zfoxrctx.html