在你真的懂Android Handler吗?(一)和你真的懂Android Handler吗?(二)这两篇文章中我们了解了Handler是如何跟线程绑定的,也知道了Handler中消息传递的机制是通过无限for循环。但是我们还遗留了一些问题,比如MessageQueue中的数据结构是怎样的?是一个先进先出的队列吗?在主线程中使用了无限for循环,为什么消息队列为空时没有引起ANR呢?这些问题需要我们通过分析MessageQueue源码来寻找答案。
对于第一个问题,MessageQueue是一个先进先出的队列吗?其实并不是,而是一个Message的单链表,MessageQueue中有一个mMessages属性,它是一个Message类型的对象,它指向了单链表的头。Message类中有一个next属性,它也是Message类型的,它指向一个message的下一个节点。下面是MessageQueue和Message的部分源码:
public final class MessageQueue {
......
//消息队列的头
Message mMessages;
//标示next()方法是否被阻塞在pollOnce()处一段时间,具体时间由non-zero-timeout决定
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
......
}
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
......
/**
* Optional field indicating the uid that sent the message. This is
* only valid for messages posted by a {@link Messenger}; otherwise,
* it will be -1.
*/
public int sendingUid = -1;
......
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;
}
下面我们通过分析向MessageQueue中插入消息和从MessageQueue中获取消息的两个方法来探索为什么MessageQueue不是一个先进先出的队列,以及在哪一步会发生阻塞。
先看插入消息的代码,向MessageQueue中插入代码要调用enqueueMessage()方法,下面是源码:
boolean enqueueMessage(Message msg, long when) {
//检查msg的有效性
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//在同步代码块中插入消息
synchronized (this) {
//如果当前messageQueue已经退出了,就回收消息,同时返回false
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//将消息标志为已使用
msg.markInUse();
//设置消息应该在什么时间被处理
msg.when = when;
//获取消息列表的头
Message p = mMessages;
//是否需要唤醒,如果取消息时消息队列中没有消息,那么获取消息的操作就会被阻塞,当插入一条消息时就需要唤醒获取消息的操作
boolean needWake;
//如果当前消息列表为空,或者要插入的消息需要马上执行,或者要插入的消息应该比列表头部的消息更早执行,那么就将该消息插到列表头部
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
//如果当前消息队列证被阻塞,那么就需要唤醒它
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
//只有当当前消息队列被阻塞了,而且列表头部消息被消息屏障阻塞(消息屏障也是一个消息,但是它的target为空),而且要插入的消息是个异步消息(即它不会被消息屏障阻塞)时才需要唤醒
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; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
//如果需要唤醒消息队列就调用nativeWake()方法
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
通过enqueueMessage()方法可以看到,Handler发送的消息并不是插在链表最后的,而是按消息应该被处理的时间顺序插入到链表中,所以MessageQueue并不是真正的队列。
而且通过这个方法我们知道在向MessageQueue中插入消息时会根据需要唤醒MessageQueue取消息的操作,这个操作是通过调用nativeWate()方法实现的,具体逻辑要看native代码,我们暂时先不考虑。
下面我们看一下从MessageQueue中取消息的代码,看看哪一步操作会被阻塞。取消息调用的是MessageQueue的next()方法,以下是源码:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//下一次阻塞的超时时间
int nextPollTimeoutMillis = 0;
//下面是获取消息的操作,它是在无限for循环中执行的
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//这一步是调用的native层的代码,有可能会被阻塞,nextPollTimeoutMillis表示最多阻塞多长时间,0-不阻塞,马上返回,-1表示无限阻塞,直到被唤醒
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;
//如果当前头节点被屏障阻塞了,就寻找下一个异步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//如果找到了异步消息,而且还没有到答该消息需要执行的时间,就计算还需要多长时间t,然后进行下一轮循环,在下一轮循环中将会被阻塞时间t
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();
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;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
从上面的代码可以看到,取消息的操作是在一个无限for循环中进行的,每次循环中都会先调用nativePollOnce(long ptr, int nextPollTimeOutMillis)方法,这个方法可能会被会被阻塞,阻塞多长时间由nextPollTimeoutMillis参数决定,0表示不阻塞,-1表示无限阻塞,直到被唤醒(被唤醒的操作也是在native进行的)。当这个方法执行完毕就会从消息链表中寻找消息,消息链表的头是由mMessages标记的,所以我们从mMessages开始依次查找,如果找到了一条消息,而且这条消息需要马上执行,就返回该消息,同时将该消息从列表中删除,如果找到的消息需要过一段时间再执行,那么就会计算还需要多长时间(假设时长为t毫秒)才能执行,然后开始下一轮循环,再次调用nativePollOnce()方法,等待t毫秒,然后再返回该消息。如果消息队列为空,那么进入下一轮循环,再次调用nativePollOnce()方法,此时该方法会一直被阻塞,直到它被唤醒。
nativePollOnce()方法被唤醒的具体逻辑是在native层的,但是我们通过前面的enqueueMessage()方法可以知道,唤醒该nativePollOnce的一个入口就是enqueueMessage()方法。当我们向消息队列中插入消息时,如果消息队列被阻塞了,就需要调用native层的方法唤醒它。
至此我们知道了,无限Looper中的无限for循环中的代码不是一直执行的,而是会在native层被阻塞,这大概也是为什么消息列表为空时没有引起ANR的原因。
这里又引出一个新问题,如果我们在Handler的消息处理中执行长时间操作会引起ANR吗?显然会,所以我们应该尽可能避免这种操作。