我们都知道,Android的Handler机制,会在线程中开启消息循环,不断的从消息队列中取出消息,这个机制保证了主线程能够及时的接收和处理消息。 通常在消息队列中(MessageQueue)中没有消息的时候,会调用MessageQueue的native方法,让主线程wait,以避免主线程在for循环中,什么也没干,白白的浪费CPU资源。 这方面的内容就不再详细描述了,感兴趣的可以看这篇博客,写的非常清晰明了。 Android消息机制-Handler 这篇文章主要还是想聊聊,线程等待和唤醒的时机的问题。
1.异步消息和同步消息
当MessageQueue中没有消息时,主线程就会进入wait状态。这句话,其实并不是那么严谨的,这是因为Message不仅有我们常用的同步消息,还有异步消息。 在Message类中,有一个标志位,来标明 该Message是异步的,还提供了对应的方法来判断Message是不是属于异步消息。
static final int FLAG_ASYNCHRONOUS = 1 << 1;
....................................
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
异步消息和我们常用的同步消息都是通过MessageQueue的enqueueMessage方法入队,next方法出队。MessageQueue对这两种信息的不同处理,取决于特殊的barrier机制。 只要是Message在走到最后的enqueueMessage方法,成功入队之前,一定要保证target不为null,(target是Message类中的其中一个属性,一般指明Message需要在哪个Handler中执行)。 当要使用异步消息时,我们必须先调用MessageQueque中的postSyncBarrier方法,将barrier插入到 MessageQueque的队头。 barrier是一个特殊的Message,他的target为null。当MessageQueue的队头被设置为barrier的使用,MessageQueue将会 忽略 队列中的同步消息,只会处理队列中的异步消息。 通过barrier机制,保证了异步消息的执行优先性。
介绍完了异步消息的机制,我们再来看消息队列中等待和唤醒的具体操作
线程的等待
线程的等待是通过调用MessageQueue的nativePollOnce()方法实现的,该方法只在Message的next方法。同时MessageQueue中还有一个mBlocked变量,来指示线程是否被阻塞。
private boolean mBlocked;
............
private native void nativePollOnce(long ptr, int timeoutMillis);
nativePollOnce()是native方法,是用c++实现的,他传入两个参数,第一个可以理解为线程标识符,可以 第二个传入的参数就是线程睡眠的时间,比如传入1,则表示在1ms后唤醒线程。如果传入的int为-1,则表示线程无限等待。
对于next方法流程的解析,已经有写的非常好的文章了,而且这个方法的流程也不太复杂,所以这里只选取与调用nativePollOnce()方法,相关的代码片段。
Message next() {
.........................
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
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 {
nextPollTimeoutMillis = -1;
}
...............................................
可以看到在next()方法中,首先会检查队列中的队头Message,若队头Message不为null,且Message的target为null,则说明队列中设置了barrier,需要优先处理异步消息。 紧接着程序会遍历队列,寻找异步消息,如果没有找到异步消息,则把Message置为null。 如果队头Message为null,说明消息队列中没有任何消息,或者是在设置Barrier后,队列中却没有任何一个异步消息,此时会将nextPollTimeoutMillis 的值设为-1,在下一次循环开始的时候,调用nativePollOnce(),传入nextPollTimeoutMillis ,线程进入wait状态。 排除了这两种情况,next方法会根据取到的Message的when的值(上面是该Message需要执行的时间),设置nextPollTimeoutMillis ,同样也在下一次循环的开始,调用nativePollOnce()。
线程的唤醒
下面是唤醒线程的过程。线程的唤醒,是通过MessageQueue中的nativewake方法实现的。 他在MessageQueue中的quit(),removeSyncBarrier()和enqueueMessage()方法中被调用。
private native static void nativeWake(long ptr);
接下来,我们就开始逐一分析nativeWake()方法的调用原因和时机。
quit()
先来看quit()方法,他的作用是清空消息队列,退出消息循环。可以根据传入的参数来决定是否安全退出(确保队列中的消息执行完)。
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
nativeWake(mPtr);
}
}
可以看到在quit()方法的最后部分,调用了nativeWake方法。为什么要在quit()的最后唤醒线程呢? 我个人的想法是,quit()方法只是退出消息循环,并不是要杀死线程,我们不希望线程在退出循环后可能会处于wait状态。 在清空消息队列后,由于Loop仍在不断调用next方法,线程很可能处于wait状态(看上面对next方法的分析),所以这时需要唤醒线程。
removeSyncBarrier()
removeSyncBarrier()方法负责移出队列中的barrier,在官方注释里也写的很清楚 如果队列不再被barrier阻塞,则会唤醒队列 下面我们看他的具体实现:
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
removeSyncBarrier()方法中的wake逻辑其实并不复杂,如果有barrier位于队头,barrier的下一条Message不属于barrier(target不为null),且排除调用quit()方法,正在进行退出操作的特殊情况,就会调用nativeWake()方法,唤醒线程。 其实当初看到这里的时候,还有一点小疑惑。如果barrier位于队头,且barrier的下一条Message为null的时候,说明消息队列为空,其实没有必要唤醒线程。后来一想,可能是为了符合 **“单一职责”**的原则吧!removeSyncBarrier()就只是负责移除barrier,消除barrier的影响,再去判断消息队列是否为空,确实是在做超出职责的事情了。
enqueueMessage()
enqueueMessage()负责将消息加入消息队列,在这个方法里在一些情况下也会调用nativeWake()方法,唤醒线程。 下面的代码就只展示方法中和唤醒线程有关的逻辑:
........................................................
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
其实在enqueueMessage()方法中,官方注释也写的很清楚了。 当传入的Message为队头消息时,会根据mBlocked变量的指示,如果线程处于wait,则唤醒线程。 否则,只有在传入的Message为队列中处于最前面的异步消息时,才会唤醒队列。
总结
Handler机制中线程wait的情况: 1.MessageQueue为空,没有消息,则会无限等待 2.MessageQueue中设置了barrier,可在消息队列中却没有找到异步消息,也会无限等待。 3.还没到需要处理的Message的指定处理时间,线程会等待一段时间后(计算处理出来的时间差),自动唤醒自己。
Handler机制中wake线程的情况: 1.调用quit(),退出消息循环的最后,会wake线程 2.移出队列中的barrier时,如果barrier正确位于队头,且下一条Message不属于barrier(target不为null)时,wake线程。(还需要排除正在quit()的情况) 3.在消息入队时,如果传入的Message为队头消息时,且线程处于wait,则会唤醒线程。 4.在消息入队时,如果传入的Message为异步消息,且位于队列的最前端,若此时线程处于wait状态,也会唤醒线程。 (注意:3和4是互斥的,由于barrier的存在,异步消息不可能位于队头)
|