来看下 loop()代码:
// Looper.java
public static void loop() {
final Looper me = myLooper(); // 1
…
final MessageQueue queue = me.mQueue; // 2
…
for (;😉 { // 3
Message msg = queue.next(); // 4
if (msg == null) { // 5
// No message indicates that the message queue is quitting.
return;
}
…
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
…
try {
msg.target.dispatchMessage(msg); // 6
} finally {
…
}
…
msg.recycleUnchecked();
}
}
上面滤去了不重要的代码。
注释1:拿到当前线程的Looper(即ui线程的Looper)
注释2:从Looper中拿出 MessageQueue
注释3:开启死循环
注释4:从 MessageQueue.next()
中获取一个 Message出来
注释5:如果取出的Message为null,则跳出死循环
注释6:根据 Message的target来通过 dispatchMessage(msg)
来发送这个消息。
也就是说 Looper.loop()
开启了一个死循环,来处理其 MessageQueue里面的每一个Message。
直到 Message为null了才跳出循环,而英文注释表示 当只有在 MessageQueue
退出的时候 Message才为空,而MessageQueue的生命和线程一样,也就是说:线程终止了,这个循环才会结束。
所以也从而验证了,这个类为什么叫Looper(循环者,不是某冠军上单)。
这里有两个问题,也是面试的时候面试官喜欢问的:
(1)为什么开了死循环,App却感受不了卡顿,也没有ANR?
逆推回去,既然App没有卡顿,也就是说 MessageQueue
有源源不断的message供主线程处理,这是因为像 AMS、WMS这些SystemServer进程在程序运行时会一直提供功能的支持,通过Binder机制,向主进程的主线程中发送消息,所以 Looper的死循环一直是工作状态,所以并不会导致卡顿。
(2)那这样一直开着死循环,不会很占CPU资源吗?
这里就要继续看代码了,我们看看上述循环中 MessageQueue.next()
做了什么.
因为 next()代码有点长,所以把其中死循环的部分分成三个部分进行讲解:
Part1 无消息处理时挂起
// MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis);
Message next() {
…
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;😉 { // 1
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); // 2
…
}
}
注释1中:可以看到的, next()
也开了个死循环,WTF?什么鬼,loop()已经是死循环,这里还来套一个死循环了???
注释2:调用 nativePollOnce(ptr, nextPollTimeoutMillis)
,这个方法非常滴重要,它是一个 native方法,我这里就不再深入到 JNI层去看C的代码了,我这边用大白话来解释这个代码的作用:
在主线程打开Looper循环前,会打开Android系统底层的一个I/O管道(Linux epoll
),如果你身边有个后台人员,你可以问他关于 epoll
的知识,epoll做的事情就是监视文件描述符的I/O事件,它是 事件驱动模型,换言之,它就是一个NIO。在没有事件时主线程挂起,有事件时主线程唤醒处理。
关于C++层的源码可以参考下这一篇:Android 中 MessageQueue 的 nativePollOnce。
函数中传入了 nextPollTimeoutMillis,有点像 Thread.sleep(mills)
。这个nextPollTimeoutMillis会在延时任务中起到作用。
这里也就回答了上节末尾的问题,为什么开死循环不会特别占用CPU的资源,是因为在没有消息的时候主线程已经挂起,有消息时才会唤醒。
Part2 返回一个消息
// MessageQueue.java
…
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; // 1
…
if (msg != null) {
if (now < msg.when) { // 2
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;