一句话总结:
一个线程最多有 1 个 Looper(消息循环器)和 1 个 MessageQueue(消息队列);主线程默认就有,子线程若需要处理消息则必须自己创建。
一、核心规则与现象
在Android的消息机制中,存在一个不可违背的铁律:一个线程(Thread)最多只能拥有一个Looper实例。当你试图为一个已经拥有Looper的线程再次创建Looper时,应用会立即崩溃。
// 错误演示:在子线程中连续调用 Looper.prepare()
new Thread(() -> {
Looper.prepare(); // 第一次调用,成功
Looper.prepare(); // 第二次调用,立即抛出 RuntimeException
Looper.loop();
}).start();
这个崩溃引出了一个核心问题:系统是如何知道当前线程已经有Looper了,并强制执行这个规则的?
二、揭秘底层机制:ThreadLocal 的“魔术”
答案就在于ThreadLocal。Looper类内部有一个静态的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的构造函数中被创建的,因此Looper和MessageQueue也是一对一的。
三、Handler如何“找到组织”?
Handler在创建时,需要知道自己应该把消息发送到哪个MessageQueue。这个过程同样依赖ThreadLocal。
当你调用无参构造函数new Handler()时,其内部会执行以下操作:
- 调用
Looper.myLooper()方法。 Looper.myLooper()的实现就是简单的sThreadLocal.get(),它从当前线程的ThreadLocal中取出Looper实例。- 如果取出的
Looper为null,就意味着当前线程没有消息循环机制,Handler无法创建,抛出我们熟悉的异常:Can't create handler inside thread that has not called Looper.prepare()。 - 如果
Looper存在,Handler就持有了它的引用,从而也知道了对应的MessageQueue在哪里。
四、从手动挡到自动挡:拥抱 HandlerThread
虽然我们可以在子线程中手动调用Looper.prepare()和Looper.loop()来创建一个消息循环,但这很繁琐且容易出错(例如忘记调用loop()或忘记在退出时调用quit())。
为此,Android提供了HandlerThread——一个已经封装好Looper的Thread。
// 推荐的子线程消息处理方式
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、广播等所有应用核心事件,是整个应用的“心脏”。