我们会使用 quit() 或 quitSafely() 终止 Looper 线程的轮循,其背后的原理和细节,今日一并了解下。
quit()
子线程可以手动调用 quit() 退出轮循。
// Looper.java
public void quit() {
// 默认是不安全的退出
mQueue.quit(false);
}
Looper 的调用实则由 MessageQueue 全权处理,包括:标记正在退出,并清空 Mesage,最后唤醒线程去处理。
// MessageQueue.java
void quit(boolean safe) {
...
synchronized (this) {
...
mQuitting = true; // 标记 quitting
// 不安全的退出将回收队列中所有 Message,并清空队列
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// 通知唤醒线程
nativeWake(mPtr);
}
}
- 退出的标记将导致后续的 sendMessage() 或 postRunnable() 将失效,直接返回 false。
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
// quitting flag 还会导致后续的 Message send 失败
return false;
}
...
}
return true;
}
- 默认是清空队列里所有 Message,包括时间正好抵达的 Message 都无法处理,不太友好。
// MessageQueue.java
// 无论 when 是否抵达一刀切,可能当前时刻本该执行的 Message 也被剔除,无法执行
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
- 唤醒的线程将进入读取队列的下一次循环,因为队列已无 Message,将返回 null。
// MessageQueue.java
Message next() {
...
for (;;) {
...
synchronized (this) {
...
// 引发取 Message 的下次循环返回 null
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
- loop() 拿到的 Message 为空,死循环退出,线程结束。
// Looper.java
public static void loop() {
...
for (;;) {
Message msg = queue.next();
if (msg == null) {
// loop() 拿到 null 则退出
return;
}
...
}
}
更推荐 quitSafely()
更推荐 quitSafely(),因其只会剔除执行时刻晚于当前时刻的 Message:保证 quit 调用的那刻,满足条件的能保留在队列当中,而不满足的 Message 则全部出队。
// MessageQueue.java
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
// 如果队首 Message 的执行时刻仍晚于当前时刻,那么全部清空
if (p.when > now) {
removeAllMessagesLocked();
} else {
// 否则遍历队列,筛选需要剔除的 Message
Message n;
for (;;) {
n = p.next;
// 没有更晚的 Message,均不需要剔除,直接返回
if (n == null) {
return;
}
// 找到队列中最前一个晚于当前时刻的 Message
if (n.when > now) {
break;
}
p = n;
}
// 前一个 Message 后全部出队
p.next = null;
// 将最前一个晚于当前时刻的 Message 及之后的 Message 回收
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
- 唤醒的 next() 循环将取出留在队列里的 Message 进行处理,同时更新队列
- 下一次 next 取不到 Message,会因为 quitting 的 flag 返回 null,告知 loop() 死循环退出
Message next() {
...
for (;;) {
...
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
...
if (msg != null) {
// 队列里的 Message 早于当前时间,进入else
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
// Message 出队并更新指向
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
// 队首后移一个节点
mMessages = msg.next;
}
// 拿到了 Message,并交给 Looper 回调
msg.next = null;
msg.markInUse();
return msg;
}
} else {
...
}
...
}
...
}
}
主线程的 Looper 如何退出
主线程创建的 Looper 指定了不允许 quit,即不可以手动调用 quit,那么如何 quit 的? Todo
// Looper.java
public static void prepareMainLooper() {
prepare(false);
...
}
private static void prepare(boolean quitAllowed) {
...
sThreadLocal.set(new Looper(quitAllowed));
}
// Main Looper 初始化的时候指定了不允许退出
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
...
}
如果强行在主线程里调用了 quit(),会发生异常:
java.lang.IllegalStateException: Main thread not allowed to quit.
// MessageQueue.java
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
...
}
-
那主线程如何退出呢?
不需要退出,在内存不足的时候 App 由 AMS 直接回收进程。
-
更不用手动 quit,原因?
主线程 ActivityThread 极为重要,承载了 Application、Activity、Service 等组件的生命周期,即便某个组件结束了它仍有存在的必要。
换言之,ActivityThread 的作用域超过了这些组件,不该由这些组件去处理它的结束。
比如,Activity destroy 了,ActivityThread 仍然要处理其他 Activity 或 Service 等组件的事务,不能结束。