HandlerExecutor 提交语义
HandlerExecutor 不是线程池,也不拥有线程。它把一个已经绑定 Looper 的 Handler 适配为 Java Executor:调用方只看到 execute(Runnable),任务仍进入原有的 MessageQueue,并由原 Handler 所在线程运行。
本文适合已经读过 Handler 消息发送、Handler 消息处理 和 HandlerThread 生命周期 的读者。重点是区分三个结果:execute() 当场拒绝、任务已被队列接受但退出前被丢弃、任务被 Looper 分发后在目标线程抛异常。本文不比较线程池调度算法,也不把 AndroidX 或第三方并发库当作 framework 实现。
1. 适配边界
源码文件:frameworks/base/core/java/android/os/HandlerExecutor.java
Android 17 的完整实现只有一个不可变字段和一个接口方法。@hide 表示它不是面向普通 SDK 应用的公开 API;源码阅读时仍可用它理解 framework 如何把 Handler 调度接到接受 Executor 的内部接口上。
public class HandlerExecutor implements Executor {
private final Handler mHandler;
public HandlerExecutor(@NonNull Handler handler) {
mHandler = Preconditions.checkNotNull(handler);
}
@Override
public void execute(Runnable command) {
if (!mHandler.post(command)) {
throw new RejectedExecutionException(mHandler + " is shutting down");
}
}
}所有权在构造时已经确定:调用者拥有 HandlerExecutor,但 Executor 不拥有 Handler、Looper、MessageQueue 或线程。final mHandler 保证适配器不会在运行中换到另一条队列;它不延长 HandlerThread 的生命周期,也不会在最后一个任务完成时自动 quit()。
构造函数通过 Preconditions.checkNotNull() 立即拒绝空 Handler。这与 execute() 的拒绝不同:前者是配置错误,尚无任务;后者是已有 Handler,但其队列已不能接受当前任务。
execute() 没有再次调用 checkNotNull(command)。源码上的 @NonNull 主要服务静态分析,并不是 Java 运行时检查;因此不能把“空 Runnable 一定在 HandlerExecutor 入口抛出 NPE”写成该实现的保证。调用者仍应遵守 Executor.execute() 的非空参数契约。
2. 线程归属
源码文件:
frameworks/base/core/java/android/os/HandlerThread.javaframeworks/base/core/java/android/os/HandlerExecutor.java
HandlerThread 是 framework 内把 worker Looper 以 Executor 形式暴露出来的直接消费者。它先得到绑定到自身 Looper 的共享 Handler,再构造 HandlerExecutor。
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
public Executor getThreadExecutor() {
if (mExecutor == null) {
mExecutor = new HandlerExecutor(getThreadHandler());
}
return mExecutor;
}这里的 Executor 不意味着并行:同一个 HandlerThread 只有一个 Looper 和一条队列,因此多个线程并发调用 execute() 后,任务仍由 worker 逐个分发。线程归属来自 Handler 构造时传入的 Looper,不来自 Executor.execute() 的调用线程。
图中的边界也解释了一个常见误解:execute() 总是异步入队;它不像 Handler.executeOrSendMessage() 那样在当前线程恰好是目标 Looper 时直接分发。因此即使 worker 自己调用这个 Executor,任务也会排到当前消息之后。
3. 入队转换
源码文件:frameworks/base/core/java/android/os/Handler.java
HandlerExecutor 不包装 Runnable,也不创建专用队列。它调用 Handler.post();后者从全局 Message 池取得 Message,把 Runnable 存入 callback 字段,并以零延迟走普通 sendMessageDelayed() 路径。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}任务所有权在成功入队前属于调用方;成功后由 MessageQueue 持有含 callback 的 Message,直到分发或回收。execute() 没有返回 Future,所以调用者无法通过 Executor 取得结果、取消句柄或完成通知;需要这些能力时,必须在 Runnable 的业务协议或另一个并发原语中表达,不能假设 HandlerExecutor 会补上。
4. 队列提交
源码文件:frameworks/base/core/java/android/os/Handler.java
sendMessageAtTime() 先读取 Handler 构造时保存的 mQueue,然后由 enqueueMessage() 写入 target、work source 和可选的异步标记,最后交给队列实现。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
Log.w("Looper", this + " sendMessageAtTime() called with no mQueue");
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}因此 HandlerExecutor 的任务仍受该 Handler 的属性约束:普通 Handler 投递同步 Message,Handler.createAsync() 构造出的 Handler 会把任务标为异步。适配器没有权限自行改变同步屏障、时间、token 或 Message 的其他字段;Executor 接口也没有暴露这些参数。
5. 成功含义
源码文件:
frameworks/base/core/java/android/os/HandlerExecutor.javaframeworks/base/core/java/android/os/Handler.java
HandlerExecutor 将 post() 的 false 转为 RejectedExecutionException,所以正常返回只表示“本次 post() 返回了 true”。它不表示 Runnable 已经开始,更不表示已经完成。
public void execute(Runnable command) {
if (!mHandler.post(command)) {
throw new RejectedExecutionException(mHandler + " is shutting down");
}
}Handler.post() 的源码注释还给出更窄的保证:即使它返回 true,若 Looper 在交付时间之前退出,任务仍可能被丢弃。对于零延迟 execute(),这仍存在并发窗口:提交线程成功离开 execute() 后,另一个线程可立刻调用 quit(),队列清理尚未分发的消息。
| 时点 | execute() 可观察结果 | Runnable 是否必然运行 |
|---|---|---|
| 队列已在退出 | 抛 RejectedExecutionException | 否,未入队 |
| 入队成功,随后立即退出 | 正常返回 | 否,待处理消息可被清理 |
| Looper 已取出消息 | 正常返回 | 已开始分发,但仍可能抛异常 |
这张表是生命周期契约,不是性能建议。调用者若需要“已执行”或“成功完成”的确认,应由 Runnable 写入 CountDownLatch、持久化状态或应用自己的结果回调;不能把 execute() 的正常返回当作完成信号。
6. 退出路径
源码文件:
frameworks/base/core/java/android/os/HandlerThread.javaframeworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
以普通 HandlerThread.quit() 为例,Looper 被请求退出后,后续消息发送会失败;Legacy MessageQueue 把 mQuitting 置位并移除队列中所有消息。该队列实现用于说明本篇的退出语义,不应据此替代其他 MessageQueue 变体的内部算法。
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
void quit(boolean safe) {
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
nativeWake(mPtr);
}
}quit() 与 HandlerExecutor 没有握手协议。调用方若拥有 HandlerThread,就应先停止新的提交者,再按任务完成要求选择 quitSafely() 或 quit(),最后用 join() 或显式完成信号等待线程结束。只保留 Executor 引用的调用方无法从它反向查询队列是否即将退出。
7. 分发异常
源码文件:
frameworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/Looper.java
一旦 Looper 取出 Message,Handler 发现 msg.callback 非空就直接运行它,不会走 Handler.Callback 或 handleMessage()。Runnable.run() 抛出的 Exception 由 Looper.loopOnce() 记录给 Observer 后重新抛出。
public void dispatchMessageImpl(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null && mCallback.handleMessage(msg)) {
return;
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}handleCallback() 没有捕获任务异常,因此控制流会返回到外层的 Looper.loopOnce() 分发边界。Looper 可以先通知已注册的 Observer,但随后仍把原异常抛出:
try {
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
}这不是 Executor 对异常的收集机制。HandlerExecutor 没有 Future,不能把 task 异常保存给提交者;未捕获异常沿 Looper 的线程级异常路径传播,可能结束 worker 循环或触发进程级处理。需要隔离单个任务失败时,Runnable 本身必须定义捕获、报告和恢复策略。
8. 测试线索
源码文件:frameworks/base/core/tests/coretests/src/android/os/HandlerThreadTest.java
Android 17 的 frameworks/base 测试树没有找到 HandlerExecutor 专用测试;不能据此虚构其对象缓存、拒绝或完成测试。可复用的底层反向证据是 HandlerThreadTest.testHandlerThread:它启动 worker、取得 Looper、用 Handler 发送消息,并断言回调运行在 worker TID 上。这个输入验证了本篇成功路径依赖的线程归属,而不是 HandlerExecutor 本身的异常转换。
final Handler h1 = new Handler(th1.getLooper()) {
public void handleMessage(Message msg) {
assertEquals(TEST_WHAT, msg.what);
assertEquals(mLooperTid, Process.myTid());
mGotMessageWhat = msg.what;
mGotMessage = true;
synchronized (this) {
notifyAll();
}
}
};
Message msg = h1.obtainMessage(TEST_WHAT);
synchronized (h1) {
h1.sendMessage(msg);
h1.wait();
}
assertTrue(mGotMessage);
assertEquals(TEST_WHAT, mGotMessageWhat);第二条可读源码证据来自 HandlerThreadTest.testUncaughtExceptionFails,但它当前被 Assume.assumeTrue(false) 跳过。测试意图是向 worker Handler 投递抛出 IllegalStateException 的 Runnable;它说明异常路径被视为线程级问题,却不能作为 Android 17 实际执行通过的断言。
9. 源码导航
源码文件:
frameworks/base/core/java/android/os/HandlerExecutor.javaframeworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/HandlerThread.javaframeworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
可以按以下顺序复查一次任务的完整路径:
rg -n "class HandlerExecutor|execute\(|RejectedExecutionException" \
frameworks/base/core/java/android/os/HandlerExecutor.java
rg -n "post\(|getPostMessage|sendMessageAtTime|enqueueMessage|handleCallback" \
frameworks/base/core/java/android/os/Handler.java
rg -n "getThreadHandler|getThreadExecutor|quit\(|quitSafely" \
frameworks/base/core/java/android/os/HandlerThread.java
rg -n "enqueueMessage|mQuitting|void quit\(" \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java复述这条链路时,应能定位 Runnable 何时进入 Message.callback,解释 RejectedExecutionException 对应的是哪一个返回值,并说明为何 execute() 正常返回仍不能证明任务完成。
10. 使用边界
HandlerExecutor 适合把“已存在的、线程归属明确的 Handler”接到只接受 Executor 的接口上。它不提供任务结果、延迟 API、取消句柄、背压、线程池扩缩容或生命周期管理。对于 worker 线程,先决定谁拥有 HandlerThread 的启动和退出,再把它适配为 Executor;反过来仅从 Executor 视角推断线程存活状态,会丢失最关键的队列边界。
