文章目錄
  1. 1. 1.消息入队
  2. 2. 2.取出消息
  3. 3. 3.消息插队

Handler源码学习(一)流程
Handler源码学习(二)Message对象池
Handler源码学习(三)MessageQueue入队插队

1.消息入队

消息队列与Message对象池的结构很像,也是通过对象之间通过next指向形成链表结构

这时候加入一个msg消息,先来看如果消息队列为空的情况

1
2
3
4
5
6
7
8
9
10
11
//判断消息队列为空时,会直接将这个msg赋值给mMessage,并将p赋值给msg.next,这时next当然时null
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;
}

假设现在队列中已经有了两个消息,这两个消息的排练是按照执行时间,如果时间相同则是按照入队先后排列。来一步步分析,可以先看后面的图,把思路理清,再看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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,从字面意思理解是前一个message的意思
Message prev;
for (;;) {
//接着进入一个死循环,将p赋值给prev,看看前面的代码可以知道,p指向的是mMessages,所以这里
//是将prev指向了mMessage,再下一次循环的时候,prev则是指向了第二个message,依次类推
prev = p;
//接着将p指向p.next也就是mMessages.next,也就是消息链表中的第二个message
p = p.next;
//接下来判断两件事
//1.p==null,说明没有下一个消息了,跳出循环
//2.p!=null,并且当前需要入队的这个message的执行时间是小于队列中这个任务的执行时间的
//也就是说这个入队的message需要比队列中的这个message先执行,也跳出循环
//3.如果这两个条件都不满足的话,则继续跟队列中的下一个消息进行对比,直到满足条件,或者到
//队列的末尾
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//跳出循环后做了两件事情
// 1.将入队的这个消息的next指向循环中获取到的应该排在这个消息之后的message
msg.next = p; // invariant: p == prev.next
//将msg前面那个message.next指向了msg
prev.next = msg;
//到这里就将一个message完成了入队
//入队的过程是线程安全的
}

MessageQueue入队.jpg

MessageQueue入队(2).jpg

2.取出消息

这里贴的不是完整代码,而是取出message的核心逻辑代码,这里其实分了两个部分,第一个部分是消息插队,这个在第三节叙述,第二个部分是正常的消息,其实取出消息的部分比较简单,注释也比较清晰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//队列的第一个message
Message msg = mMessages;
//正常取出消息
if (msg != null) {
//1.首先判断当前时间是否小于了msg的执行时间,
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;
}

3.消息插队

首先需要知道的是整个android系统其实主要是依赖消息机制来处理事件,比如说点击事件等等,都是通过handler发送消息进行处理,但是这些系统消息相较于程序自己发送的消息,应该要优先执行,所以就涉及到了消息插队

  • isAsynchronous() — 如何将系统消息和程序发送的消息区分开来

在阅读Hanlder的源码的时候,可以看到一个@hide的构造方法,传入一个布尔值,从字面意思理解是异步

1
2
3
4
5
6
7
8
9
10
11
12
public Handler(boolean async) {
this(null, async);
}
mAsynchronous = async;//赋值给mAsychronous
//消息入队的时候,msg.setAsynchronous(true);将这个msg标记为异步
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

为什么要标记为异步呢?是要新开一个线程来执行么?可是最后也是发送到looper所绑定的消息队列中啊,貌似这里并没有异步,接着看上一节提到的消息插队的源码,在这里看到调用了一个方法msg.isAsynchronous(),貌似这个布尔值并不是跟异步有关系,而是将这个消息做了一个标记而已

前面的消息入队分析可以知道,虽然被标记了系统消息,但是还是按照消息队列的规则去入队,那么如何做到优先取出这个任务呢,一步步看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Message prevMsg = null;
Message msg = mMessages;
//1.判断当前第一个消息不为空
//2.重点来了,msg.target == null,如果这个消息的target为空,看过handler源码可以知道,
//只要是通过handler发送消息,就会将这个msg的target指向发送消息的handler,否则也无法处理这个消息
//那么为什么这里会出现target为空呢?先看if代码块里面的代码
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//进入一个循环,注意while中的判断条件
do {
//不断地取下一个消息来匹配判断条件
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
//只有当msg不为空 并且当前的这个消息是异步的,也就是说是系统消息,则跳出循环。
//跳出循环后就走到了正常取消息的代码中,取出的正是这个系统消息,发现插队就成功了
}

上面的分析已经插队成功,但是还有疑点,到底是怎么进入到if代码块中的

// Stalled by a barrier. Find the next asynchronous message in the queue.

作者在这里给了一条注释,通过一个阻塞块来停止正常的队列,找到队列中的第一个系统消息

在MessageQueue中试图搜索barrier,发现一个方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//判断将这个消息插入到队列中的哪个位置
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

加入队列的代码已经很熟悉了,可以看到在这段代码中其实入队了一个没有target的消息,而最上面那个public方法中可以看到传入的when是当前的系统时间,也就是说如果调用这个方法会在消息队列的头部插入一个没有target的message,到这里思路就比较清晰了,但是这个消息肯定不能一直在队列中,否则整个队列的正常消息就永远无法处理,所以相对应还有一个remove。注释中说明,这个方法应该跟插入的方法匹配使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
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 the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}

当发送系统消息时 会在消息队列中插入一个target为空的message,在取出消息时如果发现了这个消息,就跳过所有的正常消息,返回最近的一个系统消息,然后将这个标记消息从队列中remove。

整个Handler源码学习系列笔记就完结了,当然现在也没有到特别深入的程度,但了解了整套Handler的消息机制后,相信对于源码的阅读能力和编程思路上也有挺大的提高。