Android中线程间通信机制Handler疑问解答
说起Handler的使用,几乎是开发者入门必备的开发技能。而且都会知道Handler配合一个Looper和MessageQueue来实现消息的创建、分发、处理。
每一个Handler会绑定到创建它的线程以及一个消息队列。
通过Handler,我们可以跟其他线程发消息实现线程切换,也可以给当前线程Handler发消息实现定时任务。
这里总结一下对Handler一些可能有的疑问。
消息循环时是否会阻塞主线程
会
当Looper开始循环后,会开启一个死循环,在这个循环中不停的通过next()方法从MessageQueue中读取消息,而MessageQueue的next()是一个可能会被阻塞的方法。
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 (;;) {
//当读取下一条消息,该循环可能会被next()方法阻塞住,直到读取到消息。
//如果消息为空,说明线程已退出,该循环也可以直接跳出了。
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...省略部分代码
}
}
那么MessageQueue的next()是如何在哪阻塞的呢?
答案是 nativePollOnce(ptr, nextPollTimeoutMillis);
这是一个本地C++实现的方法,ptr是native层NativeMessageQueue的指针,nextPollTimeoutMillis是一次阻塞的超时时间。
如果nextPollTimeoutMillis为0,则无需阻塞直接返回。
如果nextPollTimeoutMillis为-1,表示需要无限阻塞,直到被唤醒。
如果nextPollTimeoutMillis为其他正整数,则会阻塞相应的时间,然后返回。
Message next() {
final long ptr = mPtr;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//寻找下一条合法的消息
Message msg = mMessages;
//如果msg.target为null,代表是异步消息,要一直找到下一条同步消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//如果找到一条待处理的消息,有两种情况需要考虑
//1. 此消息的执行时间还未到,需要阻塞一段时间
//2. 此消息的执行时间已到,则返回
if (msg != null) {
if (now < msg.when) {
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();
return msg;
}
//如果消息队列中还没有消息,则无限阻塞,直达有新消息
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
注:消息在入队列时,会按照消息的执行时间点排序,所以查询到的第一条消息,如果还没有到执行的时间点没有到,那么后面的消息都不应该处理。
为什么要阻塞主线程
防止主线程退出
我们都知道一个线程是CPU最小可执行单元,当线程中代码执行完成后,该线程就会退出销毁。而Android中的主线程负责UI绘制、事件响应等,如果主线程退出了,应用就无法继续运行下去了。那么保持一个线程一直执行的方法就是无限循环。
所以当主线程没有消息需要处理时,也必须阻塞住,不能退出。
主线程阻塞机制
Linux epoll
本地方法nativePollOnce(ptr, nextPollTimeoutMillis)的核心实现就是epoll命令。
下面看epoll在Android中应用:
//system/core/libutils/Looper.cpp
//构造唤醒事件的fd
mWakeEventFd = eventfd(0, E