介绍
基于Android 9.0
MessageQueue,消息队列,在消息机制的作用是维护一个Message的队列,供Looper使用。MessageQueuede 通过一个单链表(即mMessages)来实现队列,由于Message本身就有一个指向下一个Message的Message变量next,所以MessageQueue不需要再借助其他结构来实现队列,只需要一个mMessages变量指向链表的头节点就可以了。MessageQueue中的关键方法是enqueueMessage和next,前者用于增加Message,后者用于读取Message。
enqueueMessage 重要源码
boolean enqueueMessage(Message msg, long when) {
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) {
.........
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;(1)
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {(2)
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;
}
.........
return true;
}
可以看到,enqueueMessage的作用是将一个新的Message插入到链表中正确的位置,这个链表是有序的,属性when(应处理时间)越小,位置越前,所以 (1): 如果新的Message的时间点比根节点前,就成为新的根节点,否则 (2): 插入链表中的正确位置。而在插入之前,会检查Message是不是设置了target或者是否已经使用过了,所以Handle接收到的Message不能直接再次发送出去。
next 重要源码
Message next() {
......
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
.......
nativePollOnce(ptr, nextPollTimeoutMillis);(1)
synchronized (this) {
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) {
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);(2)
} else {(3)
// 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) {(4)
dispose();
return null;
}
.......
}
........
// 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;
}
}
next中的大部分代码都在一个for循环中,以此来寻找链表中的可以处理的Message,(1)、(2) 位置的代码用于在当前没有Message需要处理时休眠(机制下节再说),所以next方法是堵塞的。在 (3) 处,获得当前时间可以处理的Message后返回;在 (4) 处,如果这个线程结束了,就返回null表示这个队列停止使用。
Linux的pipe/epoll机制
参考知乎问答:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。