Handler对于Android开发者而言,应该是再熟悉不过的了。每个Handler在创建时,就会绑定于一个特定线程内(创建时所在的线程),同时也绑定于该线程所在的Message Queue。
Handler是做什么的?
归类下来,其实是两个用处:
- 管理该线程内未来任务的执行次序。(schedule功能)
- 不同线程之间的消息通信。(包括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,就会造成内存泄露。
主要的原因其实有下面几点:
- 非静态的内部类会持有外部类的引用。如果在 activity 里直接 new 了一个 Handler,那么这个 handler 实例就会持有这个 activity 实例的引用。
- 每一个 Message 都会持有一个对应 Handler 的实例。(参考 Android Message.java 源码,可以看到每个 Message 里都有一个名为 target 的成员变量,这个 target 就是对应的 Handler) 当在 UI 线程中创建一个 Handler 时,这个 Handler 就会和 UI 线程中的消息队列进行关联,每一个发送到 UI 线程消息队列中的消息都会持有这个 Handler 的引用。
- 主线程的 Looper 生命周期是跟随 App 的,而不是某一个 Activity。因此当 activity 实例待销毁时,如果有 Message 没有处理完,Message 是会继续存在于 MainLooper所属的 MessageQueue 中的。
综上,在 activity 实例待销毁时,如果这个 activity 里存在持有 activity 引用的 Handler,并且消息队列里还有消息未处理完,就可能造成 activity 不能被回收。
规避方式:
最稳妥的方式是不要使用非静态内部类,并且如果需要持有 activity 的引用的话,要通过弱应用的方式。
网友评论