AMS Handler 分流
ActivityManagerService 并不是“一个 Handler 处理所有事情”。Android 17 的 AMS 至少把消息 分到主调度线程、system_server UI 线程和进程启动专用线程;不同 Handler 的差异首先是 Looper 所有权,其次才是消息类型。本文选择一条可完整追踪的进程启动超时路径,并用 UI 错误对话框和 ANR 辅助处理作对照,解释 Binder 调用、锁内状态、延迟消息、取消和失败清理如何分开。
本文面向已经读过 ActivityThread.H 主线程、 HandlerThread 生命周期、消息延迟边界 和 HandlerExecutor 提交语义 的读者。本文不 罗列 AMS 的全部 what 常量,也不把 system_server 的所有 ServiceThread 都当成 AMS owner; 只讨论 ActivityManagerService 文件中实际创建和消费的 Handler,以及 AnrHelper 的独立 执行器边界。
读完后,读者应能判断某个 AMS 消息属于哪个 Looper,解释进程启动超时为什么需要投递和撤销 两条消息,区分 UiHandler 显示错误 UI 与 MainHandler 修改核心状态的线程边界,并定位进程 死亡时哪些延迟消息会被移除。
1. 线程所有权
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
AMS 的正式构造函数创建一个前台优先级 ServiceThread 作为主 Handler 线程,再创建 MainHandler;同时把 UI Handler 交给 injector,并为进程启动路径保留另一条线程和 ProcStartHandler。
mHandlerThread = new ServiceThread(TAG,
THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
mUiHandler = mInjector.getUiHandler(this);
mProcStartHandlerThread = new ServiceThread(TAG + ":procStart",
THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
mProcStartHandlerThread.start();
mProcStartHandler = new ProcStartHandler(this, mProcStartHandlerThread.getLooper());测试构造器则允许注入已有 ServiceThread,把同一个 Looper 交给 MainHandler,方便隔离 AMS 状态逻辑。Handler 的线程来源因此是构造时传入的 Looper,不是消息发送方的 Binder 线程。
| Handler | Looper owner | 典型消费者 | 是否主 AMS 状态线程 |
|---|---|---|---|
mHandler | mHandlerThread | 超时、GC、进程/服务状态 | 是 |
mUiHandler | UiThread 或 injector 提供 | 错误/ANR/调试 UI | 否 |
mProcStartHandler | mProcStartHandlerThread | 进程启动专用步骤 | 否 |
2. MainHandler 构造
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
MainHandler 通过隐藏构造器创建,async=true 让它发送的消息标记异步;这只改变消息在同步 屏障下的选择资格,不改变 Handler 绑定的 ServiceThread。
final class MainHandler extends Handler {
public MainHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case GC_BACKGROUND_PROCESSES_MSG:
synchronized (mGlobalLock) {
mAppProfiler.performAppGcsIfAppropriateLocked();
}
break;
case SERVICE_TIMEOUT_MSG:
mServices.serviceTimeout((ProcessRecord) msg.obj);
break;
case PROC_START_TIMEOUT_MSG:
synchronized (mGlobalLock) {
handleProcessStartOrKillTimeoutLocked(
(ProcessRecord) msg.obj, false /* isKillTimeout */);
}
break;
}
}
}handleMessage() 中的锁不是 Handler 自动提供的同步机制。每个 case 自己决定是否需要 mGlobalLock、mProcLock 或其它 owner 锁;消息进入主线程只保证执行线程,不自动保证 AMS 状态访问安全。
3. UiHandler 构造
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
UiHandler 显式绑定 com.android.server.UiThread 的 Looper,并同样使用异步 Handler。它负责 把核心调度线程不应直接执行的错误/ANR UI 交给 system_server 的 UI 线程。
final class UiHandler extends Handler {
public UiHandler() {
super(com.android.server.UiThread.get().getLooper(), null, true);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_ERROR_UI_MSG:
mAppErrors.handleShowAppErrorUi(msg);
ensureBootCompleted();
break;
case SHOW_NOT_RESPONDING_UI_MSG:
mAppErrors.handleShowAnrUi(msg);
ensureBootCompleted();
break;
}
}
}“UiHandler 不阻塞 MainHandler”不是绝对的性能保证:两个线程仍可能通过共享 AMS 状态、 Window/UI 服务或锁互相等待。源码能够保证的是消息执行线程不同,不能保证所有下游调用都 无锁或无 I/O。
4. 消息路由
AMS 的 Binder 方法通常先完成权限和状态检查,再根据消费者选择 Handler。核心状态修改 必须遵守对应锁;UI 提示和耗时辅助工作则可以转移到其他 owner。
图中 Binder 线程只是入口,不是所有操作的执行者。真正的状态 owner 由 Handler 消费方法和 锁共同决定;不要只看到 sendMessage() 就断言“调用已异步完成”。
5. 启动超时
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
当 AMS 向新进程发送 bind application 后,会在 mHandler 上安排软超时消息,并在成功 attach 时移除相关超时。消息的 obj 是 ProcessRecord,所以超时消费者可以重新定位具体进程。
Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
msg.obj = app;
msg.arg1 = BIND_APPLICATION_TIMEOUT;
mHandler.sendMessageDelayed(msg, msg.arg1);成功 attach 的路径移除软、硬两类超时:
mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_SOFT_MSG, app);
mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_HARD_MSG, app);这里的取消匹配同时使用 what 和 app,避免一个进程的成功 attach 把另一个 ProcessRecord 的超时消息误删。消息从队列移除只是取消检查,不等于撤销进程已经执行的 工作。
6. 超时升级
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
软超时处理不是直接杀进程,而是根据状态决定是否延长并安排硬超时;硬超时最终把问题交给 AnrHelper.appNotResponding()。
private void handleBindApplicationTimeoutSoft(ProcessRecord app) {
final long hardTimeoutMillis = getBindApplicationTimeoutMillis(app);
if (hardTimeoutMillis <= 0) {
handleBindApplicationTimeoutHard(app);
return;
}
Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_HARD_MSG, app);
mHandler.sendMessageDelayed(msg, hardTimeoutMillis);
}
private void handleBindApplicationTimeoutHard(ProcessRecord app) {
final String anrMessage = "Process " + app + " failed to complete startup";
mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
}软/硬超时消息的生效点都在 MainHandler 所属线程;AnrHelper 接管后,栈转储和 ANR 记录 处理不再由这个 Handler 同步完成。
7. ANR 辅助
源码文件:frameworks/base/services/core/java/com/android/server/am/AnrHelper.java
AnrHelper 的类注释明确说明,它按需创建独立线程,避免调用方因为 ANR 处理耗时而等待。 Android 17 实现使用 expiring ThreadPoolExecutor,而不是把所有工作 post 到 AMS MainHandler。
private final ExecutorService mAuxiliaryTaskExecutor;
private final ExecutorService mEarlyDumpExecutor;
private final AtomicBoolean mRunning = new AtomicBoolean(false);
AnrHelper(ActivityManagerService service) {
this(service,
makeExpiringThreadPoolWithSize(1, sDefaultThreadFactory),
makeExpiringThreadPoolWithSize(2, sMainProcessDumpThreadFactory));
}收到 ANR 后先在 mAnrRecords 下去重、记录并提交早期 dump,再启动 consumer thread。这里 的异步边界是 Executor,不是 AMS Handler;如果把 ANR 处理误写成 mHandler.post(),会丢失 实际线程和队列模型。
8. UI 错误
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
UI Handler 的消息 payload 通常包含 ProcessRecord、错误结果或对话框状态。以 StrictMode UI 消息为例,处理方法在 mProcLock 下重新检查进程是否存在和是否已有对话框,再决定显示或直接 结束结果。
case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
synchronized (mProcLock) {
ProcessRecord proc = (ProcessRecord) data.get("app");
if (proc == null) {
Slog.e(TAG, "App not found when showing strict mode dialog.");
break;
}
if (proc.mErrorState.getDialogController().hasViolationDialogs()) {
return;
}
AppErrorResult result = (AppErrorResult) data.get("result");
if (mAtmInternal.showStrictModeViolationDialog()) {
proc.mErrorState.getDialogController().showViolationDialogs(result);
} else {
result.set(0);
}
}
ensureBootCompleted();
break;
}消息到达 UI 线程后仍要重新检查状态,因为 Binder 入口到 UI 消费之间可能已经发生进程死亡、 策略变化或对话框重复注册。Handler 只提供排队,不冻结 payload 指向的业务对象。
9. 周期消息
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
MainHandler 也用“移除旧消息再安排下一次”的方式做周期检查,例如 excessive power use。 这不是定时器线程,而是一个在 Handler Looper 上自我重排的消息协议。
case CHECK_EXCESSIVE_POWER_USE_MSG: {
checkExcessivePowerUsage();
removeMessages(CHECK_EXCESSIVE_POWER_USE_MSG);
Message next = obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
sendMessageDelayed(next, mConstants.POWER_CHECK_INTERVAL);
break;
}如果 Looper 退出或消息被清理,周期链条就会停止;如果处理本身耗时,下一次安排发生在当前 case 之后。源码没有提供固定实时周期保证。
10. 进程死亡
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
进程死亡会清理与该 ProcessRecord 相关的启动、绑定和 provider 超时消息,防止旧消息在进程 已经消失后继续操作过期状态。
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, app);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_SOFT_MSG, app);
mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_HARD_MSG, app);这些移除动作通常发生在 AMS 已经持有相应进程锁的生命周期路径中。只删除消息不等于删除 ProcessRecord 的所有引用;Activity、Service、BatteryStats 和观察者仍由各自 owner 清理。
11. 锁与回调
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
MainHandler 的 case 有的在 mGlobalLock 内调用状态方法,有的直接调用 ActiveServices; UiHandler 也会拿 mProcLock。消息切线程不能替代锁设计,反而要求读者同时看“消息线程”和 “状态锁”。
case GC_BACKGROUND_PROCESSES_MSG:
synchronized (mGlobalLock) {
mAppProfiler.performAppGcsIfAppropriateLocked();
}
break;
case SERVICE_TIMEOUT_MSG:
mServices.serviceTimeout((ProcessRecord) msg.obj);
break;如果某个 handler callback 再同步等待另一个持锁线程,就可能形成跨线程等待;本文不能从 “使用 Handler”推出没有死锁或没有长耗时。
12. 测试入口
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
AMS 提供测试构造器注入 ServiceThread 和 Injector,使测试能够控制 MainHandler 的 Looper 和依赖对象。这个设计本身就是一个可读源码入口:测试不必启动完整 system_server,也能验证 消息处理方法对状态 owner 的影响。
@VisibleForTesting
ActivityManagerService(Injector injector, @NonNull ServiceThread handlerThread) {
this(injector, handlerThread, null);
}
@VisibleForTesting
ActivityManagerService(Injector injector, @NonNull ServiceThread handlerThread,
@Nullable UserController userController) {
mHandler = new MainHandler(handlerThread.getLooper());
mHandlerThread = handlerThread;
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
}可执行验证应安排一个超时消息,推进注入 Looper,再检查对应状态方法;同时安排成功 attach 输入,确认 removeMessages(what, app) 后超时不会再次处理。它证明消息和 owner 状态关系, 不证明真实 Binder 时序或所有系统服务并发关系。
13. 复查命令
源码文件:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.javaframeworks/base/services/core/java/com/android/server/am/AnrHelper.javaframeworks/base/services/core/java/com/android/server/UiThread.java
rg -n "mHandlerThread|mHandler = new MainHandler|mUiHandler|mProcStartHandler" \
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
rg -n "class MainHandler|class UiHandler|handleMessage|SERVICE_TIMEOUT_MSG|BIND_APPLICATION_TIMEOUT" \
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
rg -n "sendMessageDelayed|removeMessages|appNotResponding|startAnrConsumerIfNeeded" \
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java \
frameworks/base/services/core/java/com/android/server/am/AnrHelper.java阅读 AMS Handler 时应按“消息常量 → payload → Handler Looper → 锁/状态 owner → 取消/恢复” 顺序走源码。只列 MainHandler 和 UiHandler 的名字,无法解释一条超时消息最终改变了哪个 ProcessRecord 或为什么成功 attach 后不会误触发 ANR。
14. 使用边界
AMS 的 Handler 分流解决的是线程归属和消息生命周期,不是自动性能优化。MainHandler 适合 短小、需要 AMS 核心状态锁的调度;UiHandler 适合 UI 消费;ANR dump 等长任务使用独立 Executor。每条消息都仍可能延迟、被移除、在消费时发现 payload 已失效,或因 Looper 退出而 不再执行。
排查 AMS 超时问题时,应检查:消息是否发到正确 Handler,payload 是否仍对应当前 ProcessRecord,成功路径是否移除了旧消息,Handler callback 是否持锁调用耗时逻辑,以及 ANR/IO 工作是否已转移到独立执行器。这样才能区分调度延迟、状态竞态和真正的服务超时。
