Looper 需要手动 quit,那主线程 Looper 呢?

本文深入剖析了Looper机制,解释了为何需要手动调用quit来终止线程,并探讨了quit与quitSafely的区别。同时,文章详细说明了主线程Looper的工作原理及其特殊性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

我们都清楚自行启动 Looper 的线程,在任务结束时需要手动调用 quit()quitSafely() 终止 Looper 轮循。但对于其中细节似乎没有仔细思考过,抽上五分钟简要学习下!

  • Looper 线程为什么要手动 quit?
  • quit 时 Message 们怎么处置?
  • quitSafely 做了哪些优化?
  • 主线程 Looper 需要 quit 吗?

Looper 线程为什么要手动 quit?

创建 Looper 并执行 loop() 的线程在任务结束的时候,需要手动调用 quit。

反之,线程将由于 loop() 的轮询一直处于可运行状态,CPU 资源无法释放。更有可能因为 Thread 作为 GC Root 持有超出生命周期的实例引发内存泄漏。

当 quit 调用后,Looper 不再因为没有 Message 去等待,而是直接取到为 null 的 Message,这将触发轮循死循环的退出。

// Looper.java
    public static void loop() {
        ...
        for (;;) {
            Message msg = queue.next();
            if (msg == null) {
                // 拿到 null 则退出
                return;
            }
            ...
        }
    }

quit 时 Message 们怎么处置?

Looper 的很多处理实则都是 MessageQueue 在发挥作用,包括这里的 Looper#quit()。它其实是调用 MessageQueue 的同名函数 quit(boolean),并指定 safe 参数为 false

// Looper.java
    public void quit() {
        // 默认是不安全的退出
        mQueue.quit(false);
    }

MessageQueue#quit() 则主要执行几项简单工作,包括:标记正在退出,并清空所有未执行的 Mesage,最后唤醒线程。

// MessageQueue.java
    void quit(boolean safe) {
        ...
        synchronized (this) {
            ...
            mQuitting = true; // 标记 quitting

            // 不安全的退出将回收队列中所有 Message,并清空队列
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // 唤醒线程
            nativeWake(mPtr);
        }
    }
  1. 退出的标记将导致后续的 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;
    }
  1. 默认的策略是清空队列里所有 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;
    }
  1. 最后唤醒线程,进入读取队列的下一次循环,因为队列已无 Message,将直接返回 null。
// MessageQueue.java
    Message next() {
        ...
        for (;;) {
            ...
            synchronized (this) {
                ...
                // 队列已无 Message
                // 将因为行退出标志的存在直接返回 null
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ...
            }
            ...
        }
    }
  1. loop() 拿到的 Message 为 null,死循环退出,线程结束。

quitSafely 做了哪些优化?

大家都知道 SDK 更推荐使用 quitSafely() 去终止 Looper,原因在于其只会剔除执行时刻 when 晚于当前调用时刻的 Message。

这样可以保证 quitSafely 调用的那刻,满足执行时间条件的 Message 继续保留在队列中,在都执行完毕才退出轮询。

  1. 调用 MessageQueue#quit(),并指定 safe 参数为 true
// Looper.java
    public void quitSafely() {
        mQueue.quit(true);
    }
  1. 安全 quit 的时候调用 removeAllFutureMessagesLocked()
// MessageQueue.java
    void quit(boolean safe) {
        synchronized (this) {
            if (safe) {
                // 安全退出的调用
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
            nativeWake(mPtr);
        }
    }

顾名思义, 其只会移除未来 Message。※未来是 when 相较于当前时刻而言,不是没执行的都叫作未来,不要误解!

// 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);
            }
        }
    }
  1. 移除未来 Message 之后唤醒线程的 next() 循环,其将取出留在队列里的 Message 进行处理。
    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 {
                    ...
                }
                
                // 队列里仍残存 Message
                // 暂时不会因为退出标志返回 null
                if (mQuitting) {
                    dispose();
                    return null;
                }
            }
            ...
        }
    }
  1. 等残存 Message 都执行完了,下一次轮询的 next() 将取不到 Message,最终因为 quitting flag 返回 null,进而触发 loop() 死循环的退出。

主线程 Looper 需要 quit 吗?

主线程 ActivityThread 创建 Looper 时指定了不允许 quit 的标志,即不可以手动调用 quit。

// 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.");
        }
        ...
    }
  • 那主线程需要 quit 吗?

    不需要,在内存不足的时候 App 由 AMS 直接回收进程。

  • 不需要 quit 的原因在于?

    主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要!

    换言之,ActivityThread 的作用域超过了这些组件,不该由这些组件去处理它的结束。 比如,Activity destroy 了,ActivityThread 仍然要处理其他 Activity 或 Service 等组件的事务,不能结束。

结语

基于回收资源或避免内存泄漏的考虑,在 Thread 完成任务后应当手动 quit。而为了确保 quit 时本可以执行的 Message 能安全执行,尽量调用 quitSafely。同时搞清楚主线程 Looper 的重要性,不需要也不可以被手动 quit!

<think>嗯,用户问的是非主线程是不是都没有Looper,即使new Thread之后过了很久。我需要先回想一下之前的知识点。之前已经解释过new Thread默认没有Looper,用户可能现在想确认是否所有非主线程都一直不会有Looper,还是说可能在之后被创建。 首先,用户可能误解Looper的存在是否和时间有关,比如线程运行一段时间后自动生成Looper,但其实Looper的创建是显式的,必须手动调用prepare和loop。所以不管线程运行多久,只要没手动创建,就不会有Looper。这点需要明确。 然后,用户可能在实际开发中遇到了在子线程使用Handler的问题,或者在阅读代码时看到别人在子线程初始化了Looper,但不确定是否所有情况都需要这么做。需要强调Looper的显式初始化步骤,并举例说明即使过了一段时间,如果不调用prepare,仍然不会有Looper。 另外,可能用户对HandlerThread的使用不太熟悉,或者不知道有这种封装好的类,所以需要提到HandlerThread的作用,作为替代方案,方便开发者使用。 还要注意用户可能担心线程生命周期的问题,比如Looper是否会因为线程长时间运行而自动创建。需要解释Looper的工作原理,loop方法会阻塞线程,保持活跃,直到退出。而普通线程如果执行完任务就会结束,没有Looper的话自然不会有消息循环。 最后,确保回答结构清晰,分点说明,并给出示例代码,帮助用户理解。同时要确认回答覆盖了用户可能的疑问点,比如时间因素、显式初始化、替代方案等。</think>在Android中,**非主线程默认没有Looper**,无论线程运行多久,除非**显式调用`Looper.prepare()`**进行初始化。以下是具体分析: --- ### 1. **Looper的初始化逻辑** - **显式创建**:Looper不会随线程运行时间自动生成。必须手动调用以下两个方法: ```java Looper.prepare(); // 创建Looper和关联的MessageQueue Looper.loop(); // 启动消息循环(线程会阻塞在此处,等待处理消息) ``` - **未初始化的表现**:即使线程运行了很长时间,若未调用`Looper.prepare()`,该线程仍**没有消息队列**,直接创建`Handler`会崩溃。 --- ### 2. **非主线程Looper状态验证** - **示例代码**: ```java new Thread(() -> { // 延迟一段时间后测试 SystemClock.sleep(5000); boolean hasLooper = Looper.myLooper() != null; // 返回false Log.d("Test", "线程是否有Looper?" + hasLooper); }).start(); ``` - **结果**:即使线程存活了5秒,`hasLooper`仍为`false`。 --- ### 3. **特殊场景:手动初始化后的Looper** - 如果主动调用`Looper.prepare()`,则线程会拥有Looper: ```java new Thread(() -> { Looper.prepare(); // 初始化Looper boolean hasLooper = Looper.myLooper() != null; // 返回true Looper.loop(); // 必须调用loop()才能处理消息 }).start(); ``` - **关键特性**:调用`Looper.loop()`后,线程会**阻塞在消息循环**中,直到调用`Looper.quit()`。 --- ### 4. **Looper与线程生命周期的关系** - **普通线程**:执行完`Runnable`任务后,线程会终止。 - **带Looper的线程**:调用`Looper.loop()`后,线程会**长期存活**(除非主动退出消息循环)。 - **常见用途**:适用于需要**持续处理异步消息**的场景(如后台任务调度)。 --- ### 5. **替代方案:HandlerThread** - Android提供`HandlerThread`类,其内部已封装了Looper初始化逻辑: ```java HandlerThread workerThread = new HandlerThread("WorkerThread"); workerThread.start(); // 内部自动调用Looper.prepare()和loop() Handler handler = new Handler(workerThread.getLooper()); // 可直接使用 ``` - **优势**:避免手动管理Looper,简化线程通信。 --- ### 结论 - **非主线程默认无Looper**,与线程运行时间无关。 - 只有**显式调用`Looper.prepare()`的线程**才会绑定Looper。 - 需要长期处理消息的线程,建议使用`HandlerThread`或手动管理Looper生命周期。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TechMerger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值