Skip to content

HandlerExecutor 提交语义

追踪 HandlerExecutor 从 execute 到 Handler.post、Message.callback 与 Looper 分发的提交、拒绝和退出边界。

基于android-17.0.0_r1
AndroidHandlerExecutorHandlerExecutor源码阅读

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 的内部接口上。

java
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.java
  • frameworks/base/core/java/android/os/HandlerExecutor.java

HandlerThread 是 framework 内把 worker Looper 以 Executor 形式暴露出来的直接消费者。它先得到绑定到自身 Looper 的共享 Handler,再构造 HandlerExecutor。

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;
}

这里的 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() 路径。

java
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 和可选的异步标记,最后交给队列实现。

java
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.java
  • frameworks/base/core/java/android/os/Handler.java

HandlerExecutor 将 post() 的 false 转为 RejectedExecutionException,所以正常返回只表示“本次 post() 返回了 true”。它不表示 Runnable 已经开始,更不表示已经完成。

java
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.java
  • frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

以普通 HandlerThread.quit() 为例,Looper 被请求退出后,后续消息发送会失败;Legacy MessageQueue 把 mQuitting 置位并移除队列中所有消息。该队列实现用于说明本篇的退出语义,不应据此替代其他 MessageQueue 变体的内部算法。

java
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.java
  • frameworks/base/core/java/android/os/Looper.java

一旦 Looper 取出 Message,Handler 发现 msg.callback 非空就直接运行它,不会走 Handler.Callback 或 handleMessage()。Runnable.run() 抛出的 Exception 由 Looper.loopOnce() 记录给 Observer 后重新抛出。

java
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,但随后仍把原异常抛出:

java
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 本身的异常转换。

java
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.java
  • frameworks/base/core/java/android/os/Handler.java
  • frameworks/base/core/java/android/os/HandlerThread.java
  • frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

可以按以下顺序复查一次任务的完整路径:

bash
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 视角推断线程存活状态,会丢失最关键的队列边界。