HandlerThread 生命周期
HandlerThread 不是线程池,也不是让任务自动并行的执行器;它是一个 Thread 子类,在自己的 run() 中创建 Looper 并进入消息循环。本文回答三个源码问题:外部线程如何安全取得 worker 的 Looper,初始化钩子何时生效,以及 quit() / quitSafely() 如何结束队列。
建议先阅读 Looper线程绑定、Looper主循环 和 NativeLooper构造。本文聚焦 Android 17 的 HandlerThread.java 与 HandlerThreadTest.java,不把它扩展成 Executor、协程或线程池的比较文章。
1. 类边界
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
类注释已经给出适用边界:只有必须使用 Handler API、且需要一个并非现有 Looper 线程的线程时才使用它;否则优先考虑 Executor 或协程。HandlerThread 提供的是单线程、单 MessageQueue、串行 Handler 调度。
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private volatile @Nullable Handler mHandler;
private volatile @Nullable Executor mExecutor;
}mTid=-1 表示尚未进入或已经离开 run();mLooper 是跨线程读取的缓存引用;mHandler 和 mExecutor 是懒创建的共享适配器。
2. 构造配置
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
onCreated();
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
onCreated();
}第二个参数使用 Process.THREAD_PRIORITY_*,不是 java.lang.Thread 的 1 到 10 优先级。这个值只在 worker 已经进入 run() 后由 Process.setThreadPriority() 应用;构造阶段不会改变当前调用线程的优先级。
3. 启动顺序
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}顺序决定了可观察状态:先记录 Linux TID,再把 Looper 绑定到当前线程;把 Looper 写入共享字段并通知等待者;然后设置线程优先级,调用初始化钩子,最后才进入阻塞的 Looper.loop()。onLooperPrepared() 运行在 HandlerThread 自己的线程上,且此时 mLooper 已经可读。
4. Looper发布
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
Looper.prepare() 写入的是当前线程的 ThreadLocal,外部线程无法通过 Looper.myLooper() 读取它。因此 run() 又把同一对象写入 mLooper,并在 synchronized (this) 内调用 notifyAll()。锁同时建立了“写入后可见”和“等待者被唤醒”的配对关系。
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}通知发生在 onLooperPrepared() 之前,所以 getLooper() 返回并不等于初始化钩子已经完成;如果调用方还需要等待子类初始化,必须使用额外状态或在 onLooperPrepared() 中建立自己的同步协议。
5. 等待读取
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
public Looper getLooper() {
if (!isAlive()) {
return null;
}
boolean wasInterrupted = false;
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
return mLooper;
}这里有三个边界:
- 线程未
start()或已经结束时,第一次检查直接返回null; - 线程已启动但
run()尚未发布 Looper 时,在对象锁上等待; - 等待期间线程死亡时,
while条件终止,不会无限等待。
InterruptedException 被方法内部消费,但退出等待前恢复调用线程的中断标志。while 而不是 if 还防止了伪唤醒和“被其他 notify 唤醒但 mLooper 仍为空”的情况。
6. 并发时序
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
调用方可以在 start() 后立即调用 getLooper();但这个保证只覆盖 Looper 发布,不覆盖 onLooperPrepared() 的业务初始化。
7. 初始化钩子
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
protected void onLooperPrepared() {
}子类可在这里创建绑定到该 Looper 的 Handler、打开线程限定资源或设置观察器。钩子返回后才进入 Looper.loop(),因此在钩子中创建的 Handler 不会与消息竞争“Looper 尚未准备”的窗口。
HandlerThread thread = new HandlerThread("worker") {
@Override
protected void onLooperPrepared() {
// 这里已经处于 worker 线程,并且 mLooper 已发布。
initializeWorkerState();
}
};如果初始化抛出未捕获异常,Looper.loop() 不会被调用,等待中的 getLooper() 可能已经拿到一个已发布但即将死亡的 Looper;调用方不能把“已返回 Looper”当作“线程一定存活到任务执行完”。
8. 共享适配器
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
public Executor getThreadExecutor() {
if (mExecutor == null) {
mExecutor = new HandlerExecutor(getThreadHandler());
}
return mExecutor;
}两个字段是 volatile,保证跨线程读取时能看到已发布引用;初始化判断本身没有锁,多个调用者可能各自创建临时 Handler/Executor,最后一个写入者胜出。它们都绑定同一个 Looper,功能正确性不依赖实例唯一性;真正需要唯一 Handler 时,应由调用方在启动同步点显式保存。
9. 立即退出
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}quit() 本身不清空对象字段,也不等待 Thread 完成;它请求 Looper 立即退出。队列中尚未交付的消息可能被丢弃,后续 Handler.sendMessage() 会失败。线程未启动或已经结束时 getLooper() 返回 null,quit() 返回 false。
10. 安全退出
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}quitSafely() 让已经到期的消息继续处理,未来的延迟消息不再等待到期。它仍然不是“等待所有已提交任务完成”的 join;正在执行的当前 callback 需要自行返回,调用方若要确认线程结束还需调用 join() 或建立完成信号。
11. 退出发布
源码文件:
frameworks/base/core/java/android/os/HandlerThread.javaframeworks/base/core/java/android/os/Looper.java
Looper.loop() 返回后,run() 才把 mTid 写回 -1:
Looper.loop();
mTid = -1;mLooper 不会在这里清零。因此线程结束后,外部可能仍然拿到一个非空 Looper 引用,但这个 Looper 已经退出,不能据此重新启动同一个 HandlerThread。判断线程生命周期应结合 isAlive() 和退出结果,而不是只检查 getLooper() != null。
12. 真实消费者
源码文件:frameworks/base/core/java/android/widget/Filter.java
Filter 的 worker 线程展示了经典的“首次请求时创建、结束时退出”模式:
HandlerThread thread = new HandlerThread("Filter");
thread.start();
mThreadHandler = new RequestHandler(thread.getLooper());结束路径调用该 Looper 的 quit(),不把 worker 线程转换成可重复启动的对象。系统服务中也常用显式优先级的 HandlerThread,将串行 IO 或状态更新隔离到独立 Looper;这仍然只提供串行化,不自动提供并发吞吐。
13. 测试输入
源码文件:frameworks/base/core/tests/coretests/src/android/os/HandlerThreadTest.java
testHandlerThread 的 arrange/action/assert 主线是:
| 阶段 | 输入或动作 | 关键断言 |
|---|---|---|
| 未启动 | 新建 HandlerThread | isAlive=false、getLooper()=null |
| 启动 | start(),子类重写 onLooperPrepared() | getLooper() 非空;钩子在另一线程执行 |
| 消费 | 用 worker Looper 创建 Handler,发送 TEST_WHAT | handleMessage() 在线程 TID 上运行且 what 正确 |
该测试证明发布同步和消息消费者线程归属;它不证明 quitSafely() 对延迟消息的完整处理顺序。
14. 读取验证
rg -n "void run\(|getLooper\(|onLooperPrepared|quitSafely|mTid" \
frameworks/base/core/java/android/os/HandlerThread.java
rg -n "testHandlerThread|getLooper\(\)|onLooperPrepared|TEST_WHAT" \
frameworks/base/core/tests/coretests/src/android/os/HandlerThreadTest.java
rg -n "new HandlerThread|thread.start\(\)|quit\(\)|getLooper\(\)" \
frameworks/base/core/java/android/widget/Filter.java \
frameworks/base/services/core/java/com/android/server/storage/StorageUserConnection.java复述时应能回答:getLooper() 为什么必须等待而不能只读 ThreadLocal?onLooperPrepared() 与 getLooper() 返回哪个先发生?为什么退出后 mLooper 非空仍不能重新使用线程?
15. 适用范围
本文覆盖 Android 17 HandlerThread 的构造、run 发布、同步读取、初始化钩子、适配器和退出生命周期。它不把 HandlerThread 当作线程池,不展开 HandlerExecutor 实现,也不声称 quitSafely() 会等待任意异步任务或回收所有外部资源。
