Android的消息机制主要指Handler的运行机制,Handler机制由底层的 MessageQueue 和 Looper 支撑,MessageQueue 是一个消息队列 Handler 通过 post 或 send 的消息就存储在这个队列中(MessageQueue 用于存储消息的数据结构并不是队列,而是一个由 Message 构成的单链表,但是我们可以按照队列去理解它),其中 Looper 恰如其意就是无限循环读取MessageQueue中的消息,有消息就拿出来处理,没有消息则阻塞在MessageQueue.next()方法中。
注:文章的中间部分是对 Handler 涉及内容的深入讲解,暂时不感兴趣的话可以直接跳至文末的 “Handler 工作过程”。
为线程实例化一个Handler
我们在实例化一个Handler时可能会遇到这样一个问题:
java.lang.RuntimeException:Can't create handler inside thread that has not called Looper.prepare()
意思是不能在一个没有调用Looper.prepare()
方法的线程中创建Handler。Looper源码的开头有这样一段注释给出了在线程中初始化 Handler 的正确操作:
/*
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }
*/
正确操作是先调用 Looper.prepare()
为线程初始化一个Looper,实例化Handler用于向该线程发消息,最后调用Looper.loop()
方法启动 Looper 的循环。
Handler的初始化过程
在Handler的构造方法中有这样一段代码:
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
代码首先要获得当前线程的 Looper 实例然后赋值给 mLooper,如果 mLooper 为空就会抛出上面说的那个异常。如果 mLooper 不为空则将 mLooper.mQueue 赋值给自己的 mQueue,这个 mQueue 就是当前线程消息队列 MessageQueue 的实例,Handler 就是通过这个实例向线程发送消息的,Looper.myLooper()
是如何获取当前线程的Looper实例的呢?进入Looper.myLooper()
的源码实现可以看到:
public static Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal 的声明和初始化:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
这是一个由 ThreadLocal 派生的专门用于获取当前线程 Looper 实例的静态对象,Handler 就是通过用它的get()
方法获取线程的Looper实例的。
ThreadLocal工作原理
ThreadLocal 是一个线程内部的数据存储类,我们通过它可以在指定线程中存储数据,数据存储后对于其他线程来说是不可见的。
每个线程都持有一个ThreadLocal.Values 实例 localValues,ThreadLocal.Values 中有一个数组Object[] table
它就是线程用来存储内部数据的容器,ThreadLocal 对外提供了一对用于存储和获得数据的方法set(T value)
和get()
,我们搞清楚两个方法的工作流程就可以知道 ThreadLocal 的工作原理了。
set 的工作流程:
首先获得当前线程的 localValues,得到 localValues 的 table 数组,以 ThreadLocal 自身实例 this 为键值计算出一个 int 值 index,在 table[index]
中存储 this 的弱引用 reference,这个 reference 在 get()
的时候会用到,在 table[index + 1]
中存储 value,这样就完成了数值 value 的存储。
get 的工作流程:
与 set 相同,get 也是要先获得当前线程的 localValues,进而得到 localValues 的 table 数组,还是用 this 计算得到的值作为数组下标得到 table[index]
并与 this.reference 作比较,相同则返回 table[index + 1]
,table[index + 1] 就是 set 时存入的 value。
Looper的工作过程
Looper的工作是从在线程中调用 Looper.prepare()
开始的,prepare 方法就是 Looper 的初始化方法,下面是 prepare 源码:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
prepare() 会调用 prepare(boolean quitAllowed) 方法,方法的 quitAllowed 参数是用来标志当前 Looper 循环是否可以通过调用 quit() 方法退出的,可以看出 prepare(boolean quitAllowed)
是私有静态方法所以我们只能用 prepare() 初始化 Looper,我们初始化的 Looper 的 quitAllowed 永远是 true,只有主线程也就是 UI 线程的 quitAllowed 为 false,也就是说我们手动开启的 Looper 循环是可以手动退出的,主线程的 Looper 是不能够手动退出的。prepare 的工作很简单,就是实例化一个 Looper 对象,Looper 对象实例化的过程中会同时实例化一个 MessageQueue 对象 mQueue,mQueue 就是这个线程中的消息队列,Looper 对象初始化后会被存入静态对象 sThreadLocal 中,sThreadLocal 就是上一节讲到的 ThreadLocal 的实例,用于存储线程对应的 Looper 对象。
Looper.prepare() 之后需要调用 Looper.loop() 来循环读取 MessageQueue 中的消息
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
loop() 方法就是开启一个无限循环调用 MessageQueue 实例 mQueue 的 next() 方法读取 MessageQueue 中的消息,当 MessageQueue 中有消息时 next 返回消息 msg,loop() 就会调用 msg.target.dispatchMessage(msg)
来处理消息,如果 MessageQueue 中没有消息 loop 循环则会阻塞在 next 方法上,这也是为什么 loop 这个无限循环不会造成 CPU 性能浪费的原因,next 是如何实现阻塞的会在下一节介绍。
loop 唯一正确跳出循环的方式是 next() 返回 null。当 Looper 的 quit 方法被调用时,Looper 会调用 MessageQueue 的 quit 方法通知 MessageQueue 退出,当 MessageQueue 被标记为退出时它的 next() 方法就会返回 null,当前线程的 Looper 也就退出了。
MessageQueue 实现
MessageQueue 读取消息的方法是 Message next()
方法,该方法内也是一个无限循环 for (;;)
,next 方法执行过程中会被阻塞在 nativePollOnce
方法上,阻塞直到 Handler 发送消息时 MessageQueue 的 enqueueMessage 方法调用 nativeWake
后才会解除,nativeWake 方法的调用也就意味着 MessageQueue 中有新消息可以返回给 Looper 处理了。显然 nativePollOnce/nativeWake 都是 native 方法,它们的底层基于名为 epoll 的 Linux 系统调用,epoll 的作用是监听某个文件描述符上是否有 IO 操作,nativePollOnce 就是在某个文件描述符上调用 epoll_wait 来监听这个文件描述符上的 IO 操作,nativeWake 会在该文件描述符上进行 IO 操作 nativePollOnce 的阻塞状态就解除了。
对 nativePollOnce 方法的讲解还没完,该方法需要传入两个参数:
final long ptr = mPtr;
int nextPollTimeoutMillis = 0;
mPtr 的值是调用 nativeInit() 方法返回的,上面讲过,nativePollOnce/nativeWake 是对统一文件描述符 IO 的监听和操作,这个 mPtr 就代表那个文件描述符;
nextPollTimeoutMillis 参数与 Handler 的 boolean sendMessageDelayed(Message msg, long delayMillis)
的 delayMillis 有关系,我们常会使用 Handler 的 postDelayed 或者 sendMessageDelayed 方法发送一个延迟消息,消息只有到指定时间才会被处理,我们在向消息队列写入消息时也就是调用 MessageQueue的 enqueueMessage 方法时方法内会判断该条消息的预约执行是否小于消息队列队首消息的预约时间,如果小于则把消息插入队首,否则插入队列中第一个预约时间大于待插入消息预约时间的消息前面,消息插入后调用 nativeWake(mPtr)
唤醒被 nativePollOnce 阻塞的 next 方法,next 的阻塞状态会被解除,如果 next 处理消息时发现这条消息 msg.when 要大于当前时间说明这是条消息是延时消息,next 会用 msg.when 减去当前时间并赋值给 nextPollTimeoutMillis,代码进入下一次循环并再次被阻塞在 nativePollOnce(ptr, nextPollTimeoutMillis)
方法,这个 nextPollTimeoutMillis 就是阻塞的定时解锁时间,为的是除延迟消息外没有新消息时阻塞也可以在延时消息到时的时候解除,next 就可以处理这条延时消息了。
Handler 工作过程
首先调用 Looper.prepare() 为线程创建一个 Looper 实例,这个实例会被存储在 ThreadLocal 中这样在线程作用域的任何位置只要调用 Looper.myLooper() 就可以得到这个 Looper 实例,Looper 的实例化过程中会实例化一个 MessageQueue 对象 mQueue,这个 mQueue 就是当前线程的消息队列。Looper prepare 后才可以创建 Handler 实例,否则会抛出找不到 Looper 的错误。接下来调用 Looper.loop() 方法开启循环读取消息并阻塞在 MessageQueue 的 next 方法直到有消息进入消息队列,现在我们就可以通过 Handler 实例向它所在线程发送消息了。
Handler 有两种发送消息的方式:post 一个 Runnable 或者 send 一条 Message,然而 post 的实现方式也是将 Runnable 封装在一个 Message 中发送出去的,sendMessage 会将 this 也就是发送消息的 Handler 对象保存在 Message 的 target 字段,用于 Looper 处理消息时找到目标 Handler,sendMessage 再通过 MessageQueue 的 enqueueMessage 方法将消息压入消息队列中,enqueueMessage 会使用 nativeWake 方法唤醒因 nativePollOnce 阻塞的 next 方法,next 的阻塞解除后会将符合要求的消息(因为有延迟消息的存在可能会重新返回阻塞状态)返回给 Looper,Looper 在接到消息后会调用
// target 就是 msg 内保存的目标 Handler 实例
msg.target.dispatchMessage(msg)
将消息交给目标 Handler 处理,dispatchMessage 的处理逻辑如下:
public void dispatchMessage(Message msg) {
// 处理 post 消息
if (msg.callback != null) {
handleCallback(msg);
} else {
// 处理以 Handler(Callback callback) 方式初始化的 Handler
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 处理
// new Handler() {
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
// }
// }
// 中的 super.handleMessage
handleMessage(msg);
}
}
至此就完整地跑完了一次 Handler 工作过程。