1. 前言
今天在面试三七互娱的时候两个面试官轮流上阵,那个时候确实是比较虚的,幸好问的都还知道。只是对于Handler
消息机制中如题的两个点,确实没有怎么注意过,也没有答出来。这里记录下。
2. 解答
平时没怎么用到过这个字段,经常用的是what
和obj
这两个字段。那么就来看下这个when
。先百度下:
what区分消息要做的事情,when表示什么时候发生的。
害,这么说来延迟消息的应该也就是一样加入到消息队列,只是标识了执行的时机,使用when
来标识。那么岂不是面试官当时连着这两个问题是在做提示?麻了。
为了验证上面的猜想,来看一下源码:
// Handler.java
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
// 延迟 =》 系统开机到当前的时间总毫秒数 + 延迟毫秒数
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) { // 队列为null对象,直接返回false
...
}
// 做一次转换,传入消息队列对象
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 入队的为Message消息,和延迟毫秒数
return queue.enqueueMessage(msg, uptimeMillis);
}
// MessageQueue.java
// 在enqueueMessage的地方传入的就是消息对象和when对象,也就是会传入延迟毫秒数
boolean enqueueMessage(Message msg, long when) {
}
从上面我们知道当有一个延迟的消息添加到消息队列的时候,首先会做一个时间的转换,计算出一个和系统开机时间相关的更大的延迟毫秒数,也就是when
。然后最终会调用enqueueMessage
方法,这个方法传入两个对象一个为Message
,另一个为when
,也就是执行的时间。那么不妨继续来看下源码:
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.when = when; // 将之间赋值到了Message对象中
Message p = mMessages; // 成员变量mMessages
boolean needWake;
// 判断下Message的执行时间
// 1. 如果没有上一个Message对象,说明是第一个消息对象,直接插入链表首部;
// 2. 如果when==0,也就是系统刚开机,且非延迟消息,直接插入到链表首部;
// 3. 如果当前延迟小于链表首部的延迟,直接添加到首部;
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.
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插入到链表中合适when的位置
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
其实也就是说,最终的执行时间其实是赋值到了Message
对象中,而在消息队列的这个链表中进行插入的时候会分情况进行判断:
- 如果当前消息队列还没有接收过消息,就直接将消息置于链表首部;
- 如果当前消息的when字段小于链表头结点的when值,就直接将消息置于链表首部;
- 这个链表是按照
when
进行排序的链表,在进行Message
插入的过程,会对消息的延迟时间来进行比较,插入到链表中合适的位置中。最终使得整个消息队列中,消息均呈现延迟有序的状态。
最终,读了源码发现和上面的猜想略有出入,但是却也相差不大。
到这里我才知道原来消息队列中,消息插入到单链表中是按照Message
的when
字段来进行对比的,按照when
的大小关系进行比较,以插入到合适的链表位置,即队列位置。
而不是一直以为的添加到队列尾部。害,一直理解错误,还要继续加油!