Looper退出与安全退出
本文承接Looper主循环,只回答一个生命周期问题:调用 quit() 或 quitSafely() 后,哪些消息还会运行,谁负责清理,为什么后续发送会失败?答案不在 Looper 的循环体里,而在 Looper 的两个薄包装和 LegacyMessageQueue.quit(boolean) 的链表处理。
1. 两个入口
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:quit()、quitSafely()
public void quit() {
clearLooperDoctor();
mQueue.quit(false);
}
public void quitSafely() {
clearLooperDoctor();
mQueue.quit(true);
}两者都先清除 LooperDoctor,再把一个布尔值传给队列。Looper 不自己遍历消息,也不等待线程结束;真正的差异由 false/true 解释。因而调用 API 的返回只代表退出请求已交给队列,不代表调用线程已经结束。
2. 立即退出
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:quit(boolean)、removeAllMessagesLocked()
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
nativeWake(mPtr);
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
mLast = null;
mAsyncMessageCount = 0;
}立即退出先把队列中的所有 Message 回收,再唤醒等待中的 nativePollOnce()。已经进入回调的消息不在链表里,不能被这段清理撤回;它是否完成取决于回调自身和线程异常处理。
3. 安全退出
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:removeAllFutureMessagesLocked()
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
mLast = p;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}安全退出以当前 SystemClock.uptimeMillis() 为界:已经到期的消息留在队列中,未来消息从第一个未到期节点开始回收。它不是“把所有消息执行完”,延迟消息即使很快到期,也可能已经在退出清理中被丢弃。
| 消息状态 | quit() | quitSafely() |
|---|---|---|
| 已在回调中 | 不撤回 | 不撤回 |
| 队列中且已到期 | 回收 | 保留,继续分发 |
| 队列中且未来到期 | 回收 | 回收 |
| 退出请求后新发送 | 拒绝 | 拒绝 |
4. 唤醒循环
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:next()
nativeWake(mPtr);
// next()
if (mQuitting) {
dispose();
return null;
}nativeWake() 让正在等待的队列立即重新检查状态。next() 看到 mQuitting 后释放 native 资源并返回 null;上一层 Looper.loopOnce() 将 null 解释为循环结束,Looper.loop() 随即返回。退出因此是“设置标志 → 唤醒 → dispose → null → 外层返回”的链路,而不是直接从另一个线程强杀 Looper 线程。
5. 发送失败
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
相关函数:enqueueMessage()
if (mQuitting) {
String msg = "The message queue is shutting down, ... target=" + msg.target;
Log.w(TAG, msg, new RuntimeException("Stack trace"));
msg.recycleUnchecked();
return false;
}退出标志一旦设置,后续消息不会重新打开队列。发送者得到 false,被拒绝的 Message 会在队列侧回收。这个返回值只说明“没有入队”,不能用来判断此前已经入队的消息是否执行完毕。
6. 重复请求
mQuitting 的检查和设置位于同一把队列锁内:第一次退出执行清理和唤醒,之后的 quit() 或 quitSafely() 直接返回。第一次调用决定清理策略,后续调用不能把立即退出改成安全退出,也不能恢复一个已经 dispose 的队列。
7. 主队列限制
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:prepareMainLooper()、quit()
// prepareMainLooper()
prepare(false);
// quit()
mQueue.quit(false);主 Looper 的 false 会保存到 MessageQueue.mQuitAllowed。因此调用主 Looper 的 quit() 或 quitSafely() 都在清理前抛出 IllegalStateException("Main thread not allowed to quit.")。普通工作线程由 prepare() 传入 true,才拥有退出能力。这个限制保护的是队列生命周期,不是对 Looper.loop() 的一般异常处理。
8. 工作线程用法
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
相关函数:run()、quitSafely()
@Override
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}HandlerThread 的线程退出仍由它持有的 Looper 完成。调用 quitSafely() 后,循环返回,run() 才会继续到结尾并把 mTid 置为 -1;所以需要等待线程真正结束时,还应使用 join(),不能把退出请求当作线程终止通知。
9. 测试路径
源码文件:frameworks/base/core/tests/coretests/src/android/os/MessageQueueTest.java
相关类型:AssertableHandlerThread
private static final class AssertableHandlerThread extends HandlerThread {
private Throwable mThrowable;
@Override
public void run() {
try {
super.run();
} catch (Throwable t) {
mThrowable = t;
}
}
public void quitAndRethrow() throws Throwable {
quitSafely();
join(JOIN_TIMEOUT);
if (isAlive()) {
throw new RuntimeException("Looper thread did not quit in 30s");
}
if (mThrowable != null) {
throw mThrowable;
}
}
}这个测试辅助类把三个动作分开:发起安全退出、等待线程结束、重新抛出线程中的异常。它验证的是“请求后最终结束且异常不被吞掉”,不是延迟消息一定执行,也不是主线程可以退出。
10. 动手验证
先定位退出实现:
rg -n "quitSafely|removeAllFutureMessagesLocked|mQuitAllowed|nativeWake" \
frameworks/base/core/java/android/os/Looper.java \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java在普通 HandlerThread 上安排一条立即消息和一条较晚消息,再调用 quitSafely():立即消息若已经到期会被保留,未来消息会被清理;随后 join() 才能确认线程已经退出。将同一实验换成 quit(),队列中的两类未执行消息都会被清理。不要在主 Looper 上运行退出实验,因为源码明确禁止该操作。
11. 边界说明
本文没有把安全退出描述成事务提交:正在执行的回调可能继续运行,回调内部新投递的消息会因 mQuitting 被拒绝,宿主线程如何处理未捕获异常也不由退出 API 决定。native poll 的 epoll 细节、Message 回收池和 HandlerThread 的 getLooper() 等待协议分别属于其他文章。
