Handler 调度边界
Handler、RxJava Scheduler 和 Kotlin Coroutine Dispatcher 都能表达“把工作安排到另一个执行 上下文”,但它们不是同一层抽象。Android 17 的 Handler 真实拥有 Looper、MessageQueue、 Message callback 和队列清理;HandlerExecutor 只是把这个队列适配成 Executor。RxJava 和 Kotlin 协程的 Scheduler/Dispatcher、Disposable/Job、operator/suspend 语义来自外部依赖, 不属于 AOSP 固定源码树。
本文面向已经读过 HandlerExecutor 提交语义、 HandlerThread 生命周期、消息队列性能代价 和 AsyncTask 任务生命周期 的读者。文章以 Android 17 的 Handler/Executor/HandlerThread 源码为可追溯基线,RxJava 与 Kotlin 协程只比较 公开抽象契约,不伪造它们的第三方源码块。重点回答:调度 owner 是谁、任务何时生效、取消覆盖 哪一段、异常交给谁、生命周期是否自动关联。
读完后,读者应能把一个“切到主线程”的需求拆成队列、执行器、流订阅或协程上下文四个层次, 判断应该保留 Handler 的精确消息控制,还是需要更高层的取消/组合语义,并知道哪些性能和 生命周期结论必须回到具体依赖版本验证。
1. 证据边界
源码文件:
frameworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/HandlerExecutor.javaframeworks/base/core/java/android/os/HandlerThread.java
Android 17 固定源码能直接证明 Handler 的 Looper/MessageQueue 行为,以及 HandlerExecutor 如何 把 Handler.post() 转成 Executor.execute()。本仓库不包含 RxJava、RxAndroid、kotlinx.coroutines 或 AndroidX 的源码,因此下面不使用第三方实现细节作为 AOSP 事实。
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");
}
}
}这段源码是本文比较的锚点:Android 的 Executor 适配器不拥有线程、不拥有 Future,成功只 表示 Handler 接受了 Runnable。任何 RxJava/协程的更强保证,都必须由各自依赖版本的实现和 文档另行证明。
2. 调度对象
源码文件:frameworks/base/core/java/android/os/Handler.java
Handler 保存 Looper、MessageQueue、Callback 和异步标志;Message 保存一次投递的 target/callback/when。调度的最小单位是 Message 或 Runnable。
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;从抽象层看,四者的 owner 不同:
| 抽象 | 主要调度对象 | 执行上下文 owner | 结果模型 |
|---|---|---|---|
| Handler | Message/Runnable | Looper + MessageQueue | boolean 入队结果 |
| HandlerExecutor | Runnable | 被包装的 Handler | 拒绝异常,无 Future |
| RxJava Scheduler | Disposable task/流通知 | Scheduler/Worker | onNext/onError/onComplete + Disposable |
| Kotlin Dispatcher | Runnable/continuation | CoroutineContext/Job | suspend 结果 + Job 状态 |
后两行是公开抽象层比较,不宣称其内部一定使用某种线程池或 Handler。Android 端具体实现应 按依赖版本源码核对。
3. 主线程切换
源码文件:frameworks/base/core/java/android/os/Handler.java
Handler 切主线程的路径是显式的:构造时绑定 Looper.getMainLooper(),调用 post() 将 Runnable 放入该 Looper 的 MessageQueue。
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> updateUi());这段代码不隐藏“何时执行”:Message 必须先入队、等待 when、被 MessageQueue.next() 取出, 再由 Looper 调 Handler.dispatchMessage()。RxJava 的 observeOn(mainScheduler) 和协程的 withContext(Main) 对调用者隐藏了类似的调度步骤,但它们是否立即执行、是否跳过 dispatch、 如何处理取消,必须看各自调度器契约。
4. 非主线程切换
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
HandlerThread 提供一条新 Looper;new Handler(worker.getLooper()) 才会把任务放到 worker, 而 new Handler() 在主线程 callback 中仍然绑定主 Looper。
HandlerThread worker = new HandlerThread("worker");
worker.start();
Handler workerHandler = new Handler(worker.getLooper());
workerHandler.post(() -> doBlockingWork());这个显式 Looper 是 Handler 的优势也是责任:读者能直接找到线程 owner,但必须自己管理 启动、停止、异常和资源。Scheduler/Dispatcher 通常把线程池或上下文选择封装在更高层,代价 是需要理解其调度器和作用域配置。
5. 延迟语义
源码文件:frameworks/base/core/java/android/os/Handler.java
Handler 的延迟 API 使用 SystemClock.uptimeMillis() 计算绝对 when。延迟到期只表示消息 具备出队资格;当前 Looper 忙碌或设备深度睡眠都会改变实际回调时间。
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}| 需求 | Handler 需要显式处理 | 高层抽象通常表达 |
|---|---|---|
| 延迟一次 | postDelayed + Runnable/token | timer/延迟 operator 或 delay |
| 重复任务 | 自己重新 post 并处理停止 | interval/循环协程 |
| 取消 | removeCallbacks/token/业务标志 | Disposable/Job cancellation |
| 现实时间唤醒 | Handler 不提供 | 仍需 AlarmManager 等系统 API |
“高层”不等于“跨深度睡眠或进程死亡仍可靠”;这些语义不应从抽象名称推断。
6. 取消范围
源码文件:frameworks/base/core/java/android/os/Handler.java
Handler 的取消 API 只覆盖仍在 MessageQueue 中、且匹配 Handler/Runnable/token 的消息;已经 由 next() 摘下或正在运行的 Runnable 无法被 removeCallbacks 撤回。
public final void removeCallbacks(@NonNull Runnable r, @Nullable Object token) {
mQueue.removeMessages(this, r, token);
}
public final void removeCallbacksAndMessages(@Nullable Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}对应比较:
| 取消问题 | Handler | RxJava | Kotlin 协程 |
|---|---|---|---|
| 队列中任务 | remove API | Disposable 通常可移除订阅任务 | Job cancellation |
| 已开始工作 | 业务标志/中断合作 | 上游是否响应 Disposable | suspend/协作式 cancellation |
| 结果晚到 | 自己检查 generation/token | stream/operator 规则 | Job 状态与结构化作用域 |
后两列只描述使用者可见的抽象,不代表所有 operator、Scheduler 或 Dispatcher 都具有相同的 抢占能力;阻塞 I/O 是否可取消仍由底层 API 决定。
7. 错误模型
源码文件:frameworks/base/core/java/android/os/Looper.java
Handler dispatch 中的未捕获 Exception 由 Looper 重新抛出;HandlerExecutor 没有 Future 或 错误回调,提交者不会自动收到 Runnable 异常。
try {
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
throw exception;
}对比时应看错误 ownership:
| 抽象 | 错误默认去向 |
|---|---|
| Handler/Runnable | Looper 线程异常处理;业务可在 Runnable 内捕获 |
| HandlerExecutor | 仍是 Handler/Looper 异常路径 |
| RxJava | 错误进入流的 onError 协议;未处理错误由库规则处理 |
| Kotlin 协程 | 异常沿 Job/CoroutineExceptionHandler/调用者结构传播 |
RxJava/协程两行不是 AOSP 源码结论;实际行为取决于版本、operator、SupervisorJob、异常处理 器和调用方式。不能把 Handler 的异常语义直接映射成 onError 或 Job cancellation。
8. 生命周期 owner
源码文件:frameworks/base/core/java/android/os/HandlerThread.java
Handler 不感知 Activity/Service 生命周期;HandlerThread owner 必须调用 quit/quitSafely, 业务还要移除消息、关闭外部资源和处理当前任务。
@Override
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
mWorker.quitSafely();
super.onDestroy();
}生命周期比较不能只写“协程自动取消”:需要明确 scope owner;RxJava 也需要持有并 dispose Disposable。三种模型都需要回答“谁持有任务,谁取消,取消后结果是否还能到达”。
9. 组合能力
源码文件:frameworks/base/core/java/android/os/HandlerExecutor.java
HandlerExecutor 的组合能力只到 Executor 接口:可以把 Handler 传给一个接收 Executor 的 framework API,但没有 map/merge/retry/Future 等更高层操作。
Executor mainExecutor = new HandlerExecutor(
new Handler(Looper.getMainLooper()));
mainExecutor.execute(() -> updateUi());| 组合需求 | Handler 常见实现 | RxJava 常见抽象 | Kotlin 协程常见抽象 |
|---|---|---|---|
| 多源合并 | 自己维护计数/状态 | merge/zip 等 operator | async/await + coroutineScope |
| 重试 | 自己保存次数并 postDelayed | retry operator | try/catch + delay |
| 背压 | 自己限流/丢弃 | Flowable/背压协议 | Channel/Flow buffer |
| 结果链 | callback/message | Observable/Single | suspend 返回值 |
这里的后两列是概念映射,不是 AOSP 中存在的代码;写具体实现时必须回到相应库版本。
10. 主线程公平
源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
Handler 消息按 when 排序,同一 Handler 的消息有队列顺序;postAtFrontOfQueue() 会把消息 插到队头并可能造成饥饿。异步消息可以穿过同步屏障,但不能抢占当前 callback。
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
}高层 Scheduler/Dispatcher 的公平性不能从 Handler 队列直接推断。RxJava 的 Scheduler、协程 Dispatcher 是否共享线程池、是否串行、是否立即执行,必须由具体实现和配置决定。
11. 调试证据
源码文件:frameworks/base/core/java/android/os/Looper.java
Handler 的调试入口包括 Printer、slow delivery/dispatch log、Observer 和 Looper dump;这些 能观察 AOSP MessageQueue 的真实边界。高层抽象还可能有自己的日志、operator assembly 或 coroutine debug,但它们不能替代 Handler 的队列证据。
looper.setMessageLogging(printer);
looper.setSlowLogThresholdMs(dispatchThreshold, deliveryThreshold);
Looper.setObserver(observer); // hidden framework API诊断“主线程没有更新”时,先看消息是否进入主 Looper,再看高层订阅/Job 是否仍活跃;否则 “observeOn/main”或“Dispatchers.Main”只说明目标抽象,不说明 Handler Message 已成功入队。
12. 版本与依赖
AOSP 固定 tag 能证明 Handler、Looper、MessageQueue 和 HandlerExecutor;不能证明当前项目使用 的 RxJava/RxAndroid/kotlinx.coroutines 版本内部是否包装 Handler、是否使用 async Message、 是否在同一线程立即执行。比较文档至少应记录:
Android: android-17.0.0_r1
RxJava/RxAndroid: 具体依赖版本和对应源码/文档
Kotlin/kotlinx.coroutines: 具体版本和对应源码/文档没有外部依赖版本时,本文只给出契约级比较,不给出内部类名、性能数字或“底层就是 Handler” 的结论。
13. 验证输入
源码文件:frameworks/base/core/java/android/os/Handler.java
第一组验证四种调度都只改变执行上下文:Handler、HandlerExecutor、RxJava main scheduler、 Coroutine Main 分别投递一个记录线程名的任务;断言目标线程符合各自配置,不比较未经控制的 实现开销。
第二组验证取消窗口:任务在入队、已出队、已开始三个时点分别取消,记录是否执行、是否收到 结果/错误。Handler 侧可直接用受控 Looper;RxJava/协程侧必须使用固定依赖版本的测试调度器。
第三组验证异常和生命周期:让任务抛异常,在各模型记录错误去向;销毁 owner 后检查 pending 任务和迟到结果。该实验只能比较可观察契约,不能用一个库的默认配置代表所有项目。
14. 复查命令
源码文件:
frameworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/HandlerExecutor.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 "postDelayed|removeCallbacks|removeCallbacksAndMessages|mQueue" \
frameworks/base/core/java/android/os/Handler.java
rg -n "run\(|getLooper|quitSafely|Looper\.loop" \
frameworks/base/core/java/android/os/HandlerThread.java
rg -n "enqueueMessage|mMessages|mBlocked|nativeWake" \
frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java15. 选择边界
Handler 适合需要直接控制 Android Looper、Message、屏障、精确取消和 framework API 互操作的 场景。RxJava 适合已有响应式流和 operator 组合的项目;Kotlin 协程适合需要 suspend、结构化 作用域和协作式取消的 Kotlin 项目——这些是抽象层选择,不是 AOSP 源码证明的性能排名。
最稳妥的比较方式是先写清任务契约:目标线程、延迟基准、取消时点、异常去向、结果所有权和 生命周期 owner。然后回到 Handler 或具体外部依赖源码验证每一项,而不是根据 API 名称推断 “谁更快”“谁自动清理”或“谁底层一定使用 Handler”。
