前言
前面从流程上探讨了Handler的工作机制,我们了解到MessageQueue是一个队列,他可以通过enqueueMessage入队消息,next出队消息。可以通过natvie层的阻塞和唤醒来实现等待和工作的切换。那对于handler来说,我们有延迟消息和即时消息,队列是怎么处理的呢?我们一起来看下队列的操作。
MessageQueue的队列操作源码
队列的操作主要是在 MessageQueue的next 和enqueueMessage方法里面,我们先来看enqueueMessage方法
enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
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 {
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);
}
}
return true;
}
我们省略一些判断代码,直接来看关于队列的操作,可以看到上来先赋值了Message p = mMessages;如果第一次进来mMessages为null,这样p就会为null,这样他就会进入到第一个判断里面,他会给自己的next赋值为p也就是null,然后给mMessages赋值了,needWake 赋值了mBlocked 是true,然后直接到了最后needwake的判断,然后调用了nativeWake唤醒的操作。这样MessageQueue的next方法就会开始读取数据。 如果p!=null,进来会判断when,when是在我们用handler.postDelayed()传进来的毫秒值加上SystemClock.uptimeMillis()当前时间,如果是0,就表示是即时消息,如果小于p.when表示他要排在p的前面,是否needWake取决于当前是否阻塞着,如果阻塞着就唤醒。没阻塞就不用唤醒。
第二次进来的时候会走到else的分支,关于isAsynchronous这个异步的判断我们平时用handler的时候他都是false,关于他的用处是在ui渲染过程中有用到,这个我们探讨到ui渲染的时候在讨论。其实我们先思考下,else的逻辑应该是拿着入队的msg的when和队列中原有的msg的when比较,如果比某一个msg的when小的话,就把他插入到那个msg的前面。我们看代码,一个for死循环,先是将p赋值给了一个prev的节点,然后取p的next赋值给自己,判断p是不是null,如果是null说明p后面没有了,就要跳出循环了,第二种情况,如果p不是null,但p.when>当前消息的when也跳出循环。跳出循环后把p赋值给入队的msg.next。把msg赋值给prev.next,这样就实现了把msg根据when放置到了合适的位置。
next取值
@UnsupportedAppUsage
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
//阻塞,注意看第二个参数,是阻塞多长时间的
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
...
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;
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
...
}
}
还是只留意了跟队列相关的代码,主要看到nativeBlock里面的第二个参数是阻塞时长,第一次是-1表示无限长,那只能等着handler post消息后的消息唤醒。我们往下走,判断msg是不是null,如果是null表示没有消息了走到else分支,这样nativeBlock又可以阻塞在可以。如果有消息,会判断当前now时间和msg.when,如果msg.when时间大的话表示还没有到消息可以发送的时间,所以他用msg.when-now 来算出要阻塞的时长,然后回到nativeBlock阻塞住。如果msg.when <=now的话就要去取出这个消息, if (prevMsg != null) 这个分支不会进去,这里跟异步消息有关系咱先不讨论他,会走else分支, mMessages = msg.next; 把msg.next赋值给mMessages,然后把msg.next置为null,然后把msg返回出去,这样队列里面就剩下mMessages作为下次要返回的消息。这样在looper里面去会把取到的msg返回给handler,这样一次完整的流程就走完了。
总结
队列的操作主要是MessageQueue的enqueueMessage和next方法,enqueueMessage是消息发送过来以后,如果是第一条消息并且阻塞住了,就直接唤醒。如果不是第一条,就对比msg.when和当前队列中的when,把他插入到>p.when的位置。其实整体来说并不难,就是一个队列的入队和出队操作,只是当有延迟消息的时候,队列按照延迟的时间来排序就可以了。
网友评论