Android消息循环的铁律:`ThreadLocal`如何保证“一线程一Looper

194 阅读3分钟

一句话总结:

一个线程最多有 1Looper(消息循环器)和 1MessageQueue(消息队列);主线程默认就有,子线程若需要处理消息则必须自己创建。


一、核心规则与现象

在Android的消息机制中,存在一个不可违背的铁律:一个线程(Thread)最多只能拥有一个Looper实例。当你试图为一个已经拥有Looper的线程再次创建Looper时,应用会立即崩溃。

// 错误演示:在子线程中连续调用 Looper.prepare()
new Thread(() -> {
    Looper.prepare(); // 第一次调用,成功
    Looper.prepare(); // 第二次调用,立即抛出 RuntimeException
    Looper.loop();
}).start();

这个崩溃引出了一个核心问题:系统是如何知道当前线程已经有Looper了,并强制执行这个规则的?


二、揭秘底层机制:ThreadLocal 的“魔术”

答案就在于ThreadLocalLooper类内部有一个静态的ThreadLocal对象,专门用于存储Looper实例。

ThreadLocal是什么?

它是一个Java并发工具,可以为每个使用该变量的线程提供一个独立的变量副本。简单来说,你往ThreadLocal里存东西,只有当前线程自己能取出来,其他线程取到的是null或它们自己存的东西。

让我们看一下Looper.prepare()的简化版源码:

public final class Looper {
    // 每个线程的Looper实例都存储在这里
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
    private final MessageQueue mQueue;

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
    }

    public static void prepare() {
        // 1. 检查当前线程是否已经有Looper
        if (sThreadLocal.get() != null) {
            // 如果有,就抛出异常,强制执行“一线程一Looper”
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 2. 如果没有,就创建一个新的Looper并存入ThreadLocal
        sThreadLocal.set(new Looper(true));
    }
}

结论:

  • Looper.prepare()的每一次调用,都会先通过sThreadLocal.get()检查当前线程是否已经关联了一个Looper
  • 如果已经存在,就证明你违背了规则,系统会毫不留情地让应用崩溃。
  • MessageQueue是在Looper的构造函数中被创建的,因此LooperMessageQueue也是一对一的。

三、Handler如何“找到组织”?

Handler在创建时,需要知道自己应该把消息发送到哪个MessageQueue。这个过程同样依赖ThreadLocal

当你调用无参构造函数new Handler()时,其内部会执行以下操作:

  1. 调用Looper.myLooper()方法。
  2. Looper.myLooper()的实现就是简单的sThreadLocal.get(),它从当前线程的ThreadLocal中取出Looper实例。
  3. 如果取出的Loopernull,就意味着当前线程没有消息循环机制,Handler无法创建,抛出我们熟悉的异常:Can't create handler inside thread that has not called Looper.prepare()
  4. 如果Looper存在,Handler就持有了它的引用,从而也知道了对应的MessageQueue在哪里。

四、从手动挡到自动挡:拥抱 HandlerThread

虽然我们可以在子线程中手动调用Looper.prepare()Looper.loop()来创建一个消息循环,但这很繁琐且容易出错(例如忘记调用loop()或忘记在退出时调用quit())。

为此,Android提供了HandlerThread——一个已经封装好LooperThread

// 推荐的子线程消息处理方式
val handlerThread = HandlerThread("MyWorker").apply { start() }

// 直接获取其Looper来创建Handler
val workerHandler = Handler(handlerThread.looper)

// 发送任务到子线程执行
workerHandler.post {
    // 这段代码将在"MyWorker"子线程中执行
}

// ...

// 在不再需要时,安全地退出循环,释放资源
handlerThread.quitSafely()

最佳实践:当需要在子线程中处理消息队列时,始终优先使用HandlerThread


五、主线程的“特权”再探

主线程之所以能直接创建Handler,是因为在应用启动时,系统在ActivityThread.main()方法中已经为它调用了Looper.prepareMainLooper()Looper.loop()。主线程的Looper不仅处理UI事件,还负责调度Activity生命周期、Service、广播等所有应用核心事件,是整个应用的“心脏”。