Skip to content

Looper退出与安全退出

从 Looper.quit 到 MessageQueue 清理,解释立即退出、安全退出、发送失败和主队列限制。

基于android-17.0.0_r1
AndroidLooperMessageQueueHandlerThread源码阅读

Looper退出与安全退出 ​

本文承接Looper主循环,只回答一个生命周期问题:调用 quit() 或 quitSafely() 后,哪些消息还会运行,谁负责清理,为什么后续发送会失败?答案不在 Looper 的循环体里,而在 Looper 的两个薄包装和 LegacyMessageQueue.quit(boolean) 的链表处理。

1. 两个入口 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

相关函数:quit()、quitSafely()

java
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()

java
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()

java
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()

java
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()

java
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()

java
// 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()

java
@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

java
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. 动手验证 ​

先定位退出实现:

bash
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() 等待协议分别属于其他文章。