#前言:
- 在上一篇Handler文章中,我们详细探讨了Handler的sendMessage函数都做了一些什么操作。
- 接下来,我们来看一看消息被Handler塞进MessageQueue中以后,后续的操作都是什么,最后又是怎么样交回给Handler的。
Looper
Looper是什么呢?
- Looper是一个消息循环器,本质上来说它就是一个死循环,它不断地在循环遍历自己内部的消息队列(MessageQueue),如果发现有消息要处理,就通知Handler处理
- 我们看到在Handler的构造中,有对Looper的一个获取:
Looper.myLooper();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//可以看到我们通过一个ThreadLocal的变量,获取到了Looper对象
}
- 消息循环器就是这样获取的,它存放在了ThreadLocal变量中,从代码上看,它是每个线程独一份的存在。也就是一个线程只能有一个Looper。
- ThreadLocal是一个比较有意思的东西,我们暂时先放下,后面会讲。
Looper创建
- 那么Looper既然是从ThreadLocal中获取的,那肯定有一个地方创建它吧?
- 简单,我们先去看一看Looper的构造器:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- Looper的构造器很有意思,其实只是简单地创建了一个MessageQueue成员。MessageQueue同学很重要啊,但是现在还没到你发炎呢,你先坐下。
- 同时Looper记录了一下,自己当前处于哪一个线程。
- 这没啥好说的,主要还是得看看谁调用了Looper的构造器,把Looper带到大家面前来的:
public static void prepare() {
//实际回调另一个重载函数
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
//它会先检查ThreadLocal中是不是已经存储了一个Looper,如果是,就报错。因为很明确的:一个线程只能存在一个Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//Looper创建好了,还要塞到ThreadLocal中去。
}
哦,找了半天,还是在Looper自己的内部,有一个静态函数prepare()。
哎呀,熟悉Android开发的同学都很了解呀,这个prepare()函数可能跟在座的各位都有不浅的关系。对,说你呢,你是不是有时候在子线程用Handler,抛出过这么一个大名鼎鼎的异常?
Can't create handler inside thread x that has not called Looper.prepare()
对,就是它,prepare()函数就是用来给线程创建一个对应的Looper对象的函数。
我们注意看下prepare函数里,实际做了一些什么骚操作:
首先去查看了sThreadLocal的get()函数,看是否能够获取到Looper对象,如果可以的话,直接抛出异常:
Only one Looper may be created per thread
- 从这里可以证明,一个线程,确定一定以及肯定,只会存在一个Looper对象。
Looper启动:
- 关于Looper,我们还有一个概念要介绍一下。Looper在构建出来以后,它毕竟是以消息循环器存在的,那这个循环肯定是要启动起来的。
- 大家也一定都还记得,Looper.loop()函数。loop()函数就是启动Looper的消息循环器进行工作,取出消息进行处理。
- 因为loop()函数颇长,我们只讨论比较关键的设计原理的部分:
public static void loop() {
final Looper me = myLooper();//获取Looper对象
if (me == null) {
//发现没,到处都是校验Looper是否创建的逻辑,没走Looper.prepare()的朋友,说你呢,别到处看
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//从Looper对象中,获取消息队列MessageQueue
...
for (;;) {
//从消息队列中,调用next()函数获取下一个消息Message对象
//这里注意了,源码中提示了一个"might block"的注释,可以暂时先理解为,可能会阻塞的意思
//前面我们也说道,MessageQueue入队操作的时候,也有一个needWake的变量,和阻塞有关
//也就是说当queue的next()函数没有消息的时候,会进行阻塞操作,避免死循环持续执行。毕竟这也会消耗大量的资源不是。
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
//队列没有消息的话,就不循环了,Looper停止
}
...
try {
//这里比较关键,msg对象调用了自己的一个名为:target的成员
//这个成员,其实就是Handler
//换句话说,每一个对象中都有一个绑定的Handler对象
//消息的处理,最终是通过msg-->Handler进行传递的
msg.target.dispatchMessage(msg);
} finally {
...
}
...
msg.recycleUnchecked();//Message对象回收
}
}
- 总结来说就是,loop()函数首先获取到一个合法的Looper对象,然后拿到Looper的MessageQueue成员。这个我们在Looper的构造器部分提到了。一个Looper只有一个MessageQueue对象。
- 消息队列MessageQueue管理着所有的消息,调用它的next()就能获取到下一个Message对象。
- Message对象我们后面会详细介绍,现在大家可以暂时理解为:Message-->Handler是绑定起来的,msg找到Handler,然后将自己回调给Handler
- loop()函数最关健的地方,就是启动Looper的消息死循环,不断地去队列里轮询消息,保证Handler发送的每一条消息都能够进行处理。
###小结
- 看到这里,Looper对象的一些关键点,我们都基本过了一遍,其实可以得到一些小总结。
- Looper在线程中,只有一个
- MessageQueue在Looper中只有一个,同理线程中也只有一个
- 子线程想要用Handler,必须调用Looper.prepare(),否则会抛异常,因为Looper是在prepare()里创建的
- 子线程想要用Handler,还要调用Looper.loop(),否则队列循环不会启动,发出的消息是无法被处理的。
##再说MessageQueue
说完Looper,我们知道了消息循环器的操作。但是还有两个个地方没有细聊,那就是
Message msg = queue.next(); // might block msg.target.dispatchMessage(msg);
他们关系到三个问题:
- MessageQueue是怎么获取下一个消息的(出队)?
- 其次是这个might block注释相关的阻塞是什么?
- 最后是消息是怎么分发消费的?
###消息出队
- 首先是消息出队,主要是在loop()函数里,调用了上述的queue.next()函数。
- MessageQueue的next()函数逻辑也比较多,我简单提炼了一些关键逻辑如下:
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);//决定是否阻塞的关键函数
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;//把mMessages取出,mMessages是在入队的时候赋值的
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//这里我觉得是一个兜底策略,当当前消息没有消费的Handler的时候
//找它的下一个合法的消息对象
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
//找到消息的时候,如果发现消息消费的时间还没到的话,设置一个阻塞唤醒时间
//以便到时唤醒执行消息
} else {
// Got a message.
mBlocked = false;
//这就是很基本的消息出队逻辑了
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();//将Message对象置为正在使用中
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;//没有新消息了,也不用再设置阻塞唤醒
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();//退出清空列表等操作,回收资源
return null;
}
...
}
...
}
}
- 这段逻辑主要其实就是队列的查询操作,找到一个当前时间能够进行分发的Message对象进行返回。
- 找到Message,也就完成了Message的出队操作。那么第一个疑问也就解决了。
- 接下来就是这个might block注释相关的阻塞是什么?
###队列的阻塞
其实,Looper的概念从一开始就有一个没有理清的概念:
- 为什么Looper.loop()中明明有一个死循环,并且在线程初始的时候就得调用,还不会导致整个线程死锁?
- 要知道死循环这个操作,难道不是变相的在调用Thread.sleep()吗?
其实消息队列其实也不是始终处于死循环的。needWake变量就是在控制是否需要调用nativeWake()函数,唤醒底层阻塞操作。
MessageQueue中的阻塞操作,其实是利用了Linux pipe/epoll机制。
简单说来,就是next()函数调用的时候,如果没有新的消息了,那就会在这个函数里阻塞起来:
nativePollOnce(ptr, nextPollTimeoutMillis);
这个函数由底层决定是否执行linux层的阻塞操作。同时传入的nextPollTimeoutMillis变量,就是当有一个指定时间操作的消息时,底层唤醒阻塞的时间变量。
这样的设计,Java层的线程就不会因为Looper的死循环而导致死锁,又能通过这种机制节约资源。
而当nextPollTimeoutMillis为-1传入的时候,是不是就永久阻塞了呢?
- 不是的。因为在前面Handler的入队操作中,还有needWake变量控制是否需要调用nativeWake(),唤醒阻塞,进行消息循环。
由此,我们基本就知道了Looper最终的结构。也明白了为什么这么一个Bug死循环,也没有阻塞线程的执行,UI线程即使有这么一个死循环,也照样画UI快的飞起。
要知道,很多面试官在聊Handler的时候,都喜欢冷不丁问出来这个问题:
诶?你说Looper是一个死循环的消息循环器,那为什么没有阻塞主线程,原理是什么
嗯,别怕,勇敢告诉他,都是假死循环!!!
###分发消息
最后一个疑问点了,Message对象也被取到了,阻塞也被唤醒了,消息是怎么回到Handler手里,交到Handler的handleMessage()手上的呢?
其实我们也看到了,关键就在loop()函数中的这一句代码:
msg.target.dispatchMessage(msg);
最后回调的是msg.target中的函数。因为target我们一早就说了是Handler在Message中的一个成员,其实就是回调Handler中的函数:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//msg中也可以设置Callback,如果设置了,就会优先回调msg中的Callback接口
//你可还记得new Handler().post(),传入了一个Runnable对象吧?这里就是优先回调它的
handleCallback(msg);
} else {
if (mCallback != null) {
//之前说到的,Handler中的mCallback对象,在构造器中初始化
//如果用户设置了Callback,就回调mCallback
if (mCallback.handleMessage(msg)) {
return;
}
}
//如果上述的回调都没有设置,就回调默认的handleMessage()
//由此可见,平常我们最常用的回调,其实优先级别是最低的
handleMessage(msg);
}
}
#总结
- 整体走下来,我们可以脉络清晰地知道Handler从创建,到消息发出,到入队、以及出队、又分发给Handler自己的handleMessage(),到底经历了哪些过程。
- 下面是Handler体系的一些简单的总结:
对象 | 作用 | |
---|---|---|
Looper | 消息循环器 | 每个线程有只能有一个 |
Message | 消息对象 | 可以有多个,但是又消息池进行复用 |
MessageQueue | 消息队列,存放Message | 每个线程有且只有一个 |
Handler | 消息发送、接收、处理着 | 一个Looper可以有多个Handler进行绑定,绑定后Handler的消息只能发给这个Looper,虽然可以多个但是不建议new太多,会内存泄漏的 |
- 关于Handler、Looper、Message、MessageQueue,我们都已经介绍完了。最后还剩下一个ThreadLocal,但是这个ThreadLocal其实不是在Android系统特有的东西,他是一个Java特性的概念。感兴趣的同学可以看我的下篇,我们慢慢再讲讲。