Android-Looper
Android-Looper
前言:本文是针对 Looper 内部的一些分析,但涉及到的知识还可能出现在以下文章中,建议都参考一遍:
1. Looper简介
首先看一下源码中对 Looper 的注释说明:
1 | /** |
大致翻译:
(1)Looper 被 Thread 用于运行一个消息循环。一个线程默认不具有与之关联的消息循环(消息队列),如果想要为线程创建一个消息队列,在该线程中调用 Looper.prepare()
,然后再调用 Looper.loop()
来启动消息处理循环,该循环将一直运行直到被终止。
(2)大多数情况下,和消息循环的交互是通过 Handler
完成的。
(3)以下代码是实现一个具有消息循环的线程的典型方式,通过分别调用 prepare()
和 loop()
来创建一个初始化的 Handler
并用于和 Looper
交互:
1 | class LooperThread extends Thread { |
(4)Looper 类中包含某些代码,是需要通过 MessageQueue 来设置和管理事件循环的。影响消息队列的状态的 API 应该在 MessageQueue 或 Handler 中而不是在 Looper 本身定义,例如 Idle Handler
和同步障栅是在消息队列中定义的,而 Thread 的前期准备、消息循环、退出则是在 Looper 中完成的。
以上内容可能涉及到 Handler 或 Message 的内容,可以通过系列对应的文章查阅。
2. Looper初始化和实例化
Looper 类光是从名字就能看出来作用,通过之前对 Handler 的源码分析可以知道,Handler 构造时,可以手动传入一个 Looper 对象和处理消息的回调 Callback,用来与 Handler 绑定,此时这个 Handler 则会用来处理该 Looper 的消息队列中的消息,并且与该 Looper 处在同一线程,而默认构造方法则最终都调用了以下构造方法,并且内部给 Looper 传入了一个 null
:
1 | /** |
可以看到默认情况下会调用 Looper.myLooper()
,再查看一下这个方法:
1 | /** |
这个方法也就是从 ThreadLocal
中获取一个对象,ThreadLocal 的相关内容在系列文章中已有介绍,简单来说,首先需要向当前线程中 set()
一个对象,才能 get()
出来,并且 ThreadLocal 中存放的对象是和线程绑定的,不同线程只能 get()
到该线程自己 set()
进去的对象,再看一下这个 sThreadLocal
在一开始的声明:
1 | // sThreadLocal.get() will return null unless you've called prepare(). |
说明存取的都是 Looper 对象。Looper 其实有两个 prepare()
方法,一个无参,另一个就是以下这个有一个布尔类型参数的,但无参的 prepare()
最终也是调用的有参这个:
1 | /** Initialize the current thread as a looper. |
在 Looper.prepare(boolean)
方法中有这么一段:
1 | private static void prepare(boolean quitAllowed) { |
到这里就清楚了,Looper 在调用 prepare()
方法后,会往对应的 ThreadLocal 中存放一个实例化的 Looper 对象,而 Looper.myLooper()
则是从 ThreadLocal 中取出这个对象,因此如果一个 Looper 没有先调用 prepare()
就直接使用会报错。但是为什么不直接通过 new
来生成实例对象呢?因为 Looper 的构造方法是私有的:
1 | private Looper(boolean quitAllowed) { |
这么设计的好处我个人理解为:① Looper、Handler、Thread 这些类都是由系统进行资源管理的,用户不应具有太大的修改权限,并且 Looper 类是 final
修饰的,也不允许用户继承重写。② Looper 必须和指定的线程绑定,指定调用 prepare()
方法可以强调这一特性。③ Looper 只能和一个线程绑定,使用 ThreadLocal 管理,采用 new
的方式可能会有多线程的问题。
3. Looper总结
其实到这里 Looper 的工作原理和流程就已经可以总结了:
- Looper 是一个消息循环。
- Looper 内有成员变量 MessageQueue,并通过它循环取出、派发消息进行事件处理。
- Looper 无法通过构造方法实例化,而是通过
prepare()
方法,并在内部调用了myLooper()
方法来实例化一个 Looper 对象,且会和线程绑定,通过对应线程的 ThreadLocal 来存取。 - Looper 所在的线程决定了 Handler 处理消息时所在的线程。实例化 Handler 时,可以自行创建某线程的 Looper 实例化对象,并将其与 Handler 绑定,则 Handler 处理的消息即来自于 Looper 所在的线程。
- 线程默认是不具有消息循环的,也即默认情况下一个 Thread 是不会维护 Looper 的,通过继承重写 Thread,并在其中调用
Looper.prepare()
来创建消息循环,再通过Looper.loop()
来开启循环。
但在实际开发中其实有些比较常见的问题:
(1)平时用 Handler 处理消息时,并没有调用 Looper.prepare()
或通过其手动创建一个 Looper 对象,但依然可以正常处理消息。这是因为,Android 的主线程即 UI 线程,有一定的特殊性,整个 App 过程中仅允许存在一个 UI 线程,而 App 的主线程对应在 ActivityThread.main()
方法,其也是整个 App 的入口方法,在这个方法里面就生成了 Looper 的实例:
1 | Looper.prepareMainLooper(); |
对应 Looper 中的源码为:
1 | /** |
虽然本质上仍然是通过 prepare(boolean)
方法生成了一个成员变量 sMainLooper
,但是加了类锁,并且只允许创建一次,由于 App 运行期间,主线程一直存在,因此主线程的对应的 Looper 实例对象 sMainLooper
在 App 运行期间有且仅有一个,所以在主线程中使用 Handler 无需手动创建 Looper 实例对象以及显式调用 prepare()
:
1 | // 主线程中实例化 Handler |
(2)即使在子线程中,也可以不显式调用 prepare()
方法,这时 Handler 的初始化方式为:
1 | private Handler mainHandler = new Handler(Looper.getMainLooper()) { |
这里是
void handleMessage(Message)
和(1)中使用匿名 Callback 的boolean handleMessage(Message)
不同。
这里不需要显式调用 prepare()
的原因是给 Handler 传了一个 Looper.getMainLooper()
的参数,源码如下:
1 | /** |
可以看到实际上就是返回了主线程的 sMainLooper
,所以也不需要手动创建 Looper 实例对象。
(3)利用 Handler 处理消息所在线程由其实例化时传入的 Looper 对象决定 这一特性,可以衍生出主线程和子线程之间交互的方式:
- 子线程向主线程发送消息:在子线程实例化 Handler 时传入
Looper.getMainLooper()
参数。 - 主线程向子线程发送消息:在主线程实例化 Handler 时传入一个在子线程中调用过
prepare()
方法已初始化的 Looper 对象。
针对以上情形 ②,主线程开启子线程后向下执行,如果主线程发送消息的时间较早,可能子线程还没有完成对 Looper 的实例化,则会导致空指针异常,此时可以将子线程用 HandlerThread
类代替,实例化时传入一个 String
类型的线程标记名,HandlerThread 不能重写 run()
方法,当然也不需要显式调用 prepare()
和 loop()
,通过 HandlerThread.getLooper()
即可获取 Looper 实例对象。