PackageWatchdog 健康监控
本文面向已经了解 PMS、RollbackManager 和 system_server 生命周期的读者。它解决的问题是:一次崩溃、ANR、显式健康检查失败或 system_server 重启,如何进入 PackageWatchdog,怎样在多个恢复 observer 之间选择动作,最后由 Rollback 或 RescueParty 执行什么缓解。本文不介绍应用崩溃本身如何由 AMS 产生,也不替代 RollbackManager 回滚 的安装事务细节。
Android 17 的源码 owner 位于 platform/packages/modules/CrashRecovery 模块,而不是 PMS 服务目录。读完后,读者应能从 notifyPackageFailure 追到 observer 回调,从 noteBoot 追到 boot loop 阈值,并解释为什么“恢复策略已选择”不等于“回滚已完成”。
1. 监控边界
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/PackageWatchdog.java。
PackageWatchdog 是失败数据的分派器。它维护 observer 和被观察包的状态,不直接删除数据、不直接执行回滚。每个 PackageHealthObserver 返回一个用户影响等级,watchdog 选择影响最小且允许执行的 observer。
失败原因常量是 UNKNOWN、NATIVE_CRASH、EXPLICIT_HEALTH_CHECK、APP_CRASH、APP_NOT_RESPONDING 和 BOOT_LOOP。其中 native crash 和显式健康检查走立即缓解路径;普通 app crash/ANR 先经过每个 monitored package 的失败窗口。
2. Observer 注册
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/PackageWatchdog.java;符号:registerHealthObserver、startExplicitHealthCheck。
public void registerHealthObserver(Executor executor,
PackageHealthObserver observer) {
synchronized (sLock) {
ObserverInternal internalObserver =
mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
internalObserver.registeredObserver = observer;
internalObserver.observerExecutor = executor;
} else {
internalObserver = new ObserverInternal(
observer.getUniqueIdentifier(), new ArrayList<>());
internalObserver.registeredObserver = observer;
internalObserver.observerExecutor = executor;
mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
syncState("added new observer");
}
}
}observer 的唯一标识是状态 key。重启后 observer 需要再次注册同一个 identifier,watchdog 才能把 XML 中保存的 ObserverInternal 绑定回活对象。isPersistent() 只影响 observer 在所有 package 过期后是否保留,不代表回调对象跨进程持久存在。
startExplicitHealthCheck 要求 observer 先注册;未注册会抛 IllegalStateException。它把包加入 observer 的监控表,并把监控总时长设置为调用者传入的 timeout;小于 1 的 timeout 会退回默认两天。新增包前后各调用一次 syncState,避免新包的截止时间晚于已有包却等待过长。
3. 失败分派
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/PackageWatchdog.java;符号:notifyPackageFailure。
public void notifyPackageFailure(List<VersionedPackage> packages,
@FailureReasons int failureReason) {
if (packages == null) {
Slog.w(TAG, "Could not resolve a list of failing packages");
return;
}
synchronized (sLock) {
long now = mSystemClock.uptimeMillis();
if (now >= mLastMitigation
&& (now - mLastMitigation) < getMitigationWindowMs()) {
Slog.i(TAG, "Skipping notifyPackageFailure mitigation");
return;
}
}
mLongTaskHandler.post(() -> {
synchronized (sLock) {
boolean immediate = failureReason == FAILURE_REASON_NATIVE_CRASH
|| failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK;
if (immediate) {
handleFailureImmediately(packages, failureReason);
} else {
// 每个包遍历所有 observer,选择最低 impact
}
}
});
}入口只做空列表保护和 mitigation cooldown,然后把重活交给 long-task handler。mLastMitigation 的默认窗口是 5 秒;它防止短时间内连续失败让多个 observer 同时执行恢复。这个窗口不是失败计数窗口,不能用它替代下面的 1 分钟滑动窗口。
普通失败对 packages 中的每个 VersionedPackage 单独选择 observer。observer 必须先通过 notifyPackageFailureLocked 表示自己正在观察该包,再由 onHealthCheckFailed 返回影响等级;返回 0 表示没有可用缓解。选择完成后,watchdog 在允许的情况下同步调用 onExecuteHealthCheckMitigation。
4. 滑动窗口
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/PackageWatchdog.java;内部类:MonitoredPackage.onFailureLocked。
默认参数是:失败计数窗口 1 分钟、触发次数 5 次、观察总时长 2 天、缓解降级窗口 1 小时。
public boolean onFailureLocked() {
long now = mSystemClock.uptimeMillis();
mFailureHistory.addLast(now);
while (now - mFailureHistory.peekFirst()
> mTriggerFailureDurationMs) {
mFailureHistory.removeFirst();
}
boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
if (failed) {
mFailureHistory.clear();
}
return failed;
}算法不是“累计五次后永久失败”,而是每次把过期时间戳从队首删除,再判断当前窗口大小。达到阈值后立即清空历史,下一轮需要重新积累。阈值触发只让 observer 进入候选集合,真正是否执行还要经过 impact limit 和 cooldown。
每次成功选择 observer 后,noteMitigationCallLocked 记录调用时间。getMitigationCountLocked 删除一小时前的记录,把窗口内次数传给 observer,用于逐步升级或降级恢复策略。
5. 立即路径
native crash 和显式健康检查失败不会等待五次普通失败。handleFailureImmediately 让所有已注册 observer 直接返回 impact,再选择最小 impact 的一个 observer。这样做是因为 native 崩溃可能来自系统关键组件,显式健康检查则已经是上游服务确认过的失败。
allowMitigations 还会检查系统属性 major_user_impact_level_threshold。impact 小于阈值才能执行;包名在豁免集合中时可以越过阈值。因此 observer 返回 90 并不等于一定回滚,属性和豁免配置是最终门槛。
6. 监控状态机
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/PackageWatchdog.java;内部类:MonitoredPackage。
显式健康检查有 INACTIVE、ACTIVE、PASSED、FAILED 四个状态。新包初始为 INACTIVE,只有 ExplicitHealthCheckController 提供检查能力并设置了 duration 后才进入 ACTIVE。收到通过回调后进入 PASSED;检查超时或总监控时长耗尽则进入 FAILED。
private int updateHealthCheckStateLocked() {
if (mHasPassedHealthCheck) {
mHealthCheckState = HealthCheckState.PASSED;
} else if (mHealthCheckDurationMs <= 0
|| mDurationMs <= 0) {
mHealthCheckState = HealthCheckState.FAILED;
} else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
mHealthCheckState = HealthCheckState.INACTIVE;
} else {
mHealthCheckState = HealthCheckState.ACTIVE;
}
return mHealthCheckState;
}pruneObserversLocked 依据最近一次 sync 的 uptime 扣减所有 monitored package 的 duration。一个包从未收到通过回调而进入 FAILED 时,watchdog 以 FAILURE_REASON_EXPLICIT_HEALTH_CHECK 调用 observer。总时长到期后包从 observer 删除;persistent observer 即使没有包也不会被丢弃。
7. 健康检查
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/ExplicitHealthCheckController.java。
PackageWatchdog.onPackagesReady 设置三个回调:单包通过、支持包集合变化、请求同步。controller 负责把请求送到实现 ExplicitHealthCheckService 的组件。通过回调会调用 onHealthCheckPassed(packageName),将所有 observer 中同名 monitored package 标为 PASSED,并重新同步请求。
包存在但没有可用 health-check service 时保持 INACTIVE,不会凭空进入 ACTIVE。只有 controller 报告支持并且请求已同步,倒计时才代表一次真实主动检查。
8. Boot loop
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/PackageWatchdog.java;内部类:BootThreshold、方法:noteBoot。
PackageWatchdog 统计的是 system_server 重启循环,不是设备每次完整 reboot。默认阈值为 10 分钟内 5 次;完整重启由 init/系统属性提供 rescueBootCount、rescueBootStart 等持久值。达到阈值后,所有 observer 的 onBootLoop 返回 impact,watchdog 选择最低影响的 observer,再同步调用 onExecuteBootLoopMitigation。
如果此前窗口内已经执行过 mitigation,后续重启从第二次开始可以再次触发,而不必重新等待五次。这是为了让已开始的恢复流程在连续 system_server 重启时继续推进。boot mitigation count 通过 metadata 文件跨越一次重启传递,避免 observer 丢失升级级别。
9. 回滚观察者
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/rollback/RollbackPackageHealthObserver.java。
Rollback observer 的 onHealthCheckFailed 会读取可用 rollback,并按 ROLLBACK_USER_IMPACT_LOW/HIGH 计算影响:低影响 rollback 通常返回 30;有低影响 rollback 但不匹配失败包时返回 70;没有可用 rollback 返回 0。native crash 的低影响 rollback 可以直接作为候选,因为失败包名可能来自 sys.init.updatable_crashing_process_name 而不是普通 VersionedPackage。
执行时,native crash 会回滚全部低影响 rollback;普通包失败优先回滚与 failed package 匹配的低影响 rollback,否则回滚全部低影响 rollback。boot loop 调用 triggerLeastImpactLevelRollback:低影响全部回滚,高影响则默认一次只回滚一个,并受 disable_high_impact_rollback 属性控制。
@Override
public int onExecuteHealthCheckMitigation(
VersionedPackage failedPackage,
int rollbackReason, int mitigationCount) {
List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
mHandler.post(() -> rollbackAllLowImpact(
availableRollbacks, failedPackageName, rollbackReason));
return MITIGATION_RESULT_SUCCESS;
}
// 普通失败优先匹配 failedPackage 的低影响 rollback
...
return MITIGATION_RESULT_SUCCESS;
}返回 MITIGATION_RESULT_SUCCESS 表示 observer 已接受并排队恢复,不代表 RollbackManager.commitRollback 已经成功。staged rollback 还会加入 mPendingStagedRollbackIds,防止所有待处理 session 完成前贸然 reboot。
10. 持久化与调度
PackageWatchdog 使用 AtomicFile 保存 observer XML;文件保存 monitored package 的名称、剩余监控时长、explicit health-check 时长、通过标志和 mitigation call 时间,不保存普通失败历史和阈值计数。启动时重新加载 XML,再由 observer 注册恢复回调对象。
状态同步由 short-task/long-task handler 分工:failure dispatch、observer 注册和磁盘写入不在调用线程执行;syncState 根据所有包最短剩余 duration 安排下一次 prune。shutdown/reboot 广播会调用 writeNow,取消异步保存并在关机线程中完成最后一次写盘。
11. RescueParty 竞争
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/RescueParty.java。
RescueParty 也注册为 PackageHealthObserver。当 app crash/ANR 达到阈值时,它可能返回比 rollback observer 更低的 impact,并执行限制级别、重启或更高等级恢复。watchdog 不按 observer 注册顺序选择,而是比较每个 observer 的 impact;返回 USER_IMPACT_LEVEL_0 的 observer 被视为没有可用动作。
因此调试“为什么没有回滚”时,不能只看 RollbackManager:先检查 PackageWatchdog 是否把 RescueParty 选成更低影响方案,再检查 allowMitigations 是否因 impact threshold 阻止执行。
12. 源码练习
- 从
notifyPackageFailure追踪一次 app crash:指出 cooldown、long-task handler、MonitoredPackage.onFailureLocked、impact 比较和最终 observer 回调的顺序。 - 构造四次失败分散在两分钟内、五次失败集中在一分钟内两组输入,判断哪组会清空 failure history 并触发 observer。
- 对比 native crash 与 app crash:说明为什么前者跳过普通阈值,为什么 Rollback observer 不能依赖普通
VersionedPackage找到失败包。 - 给定 system_server 在十分钟内反复重启,追踪
BootThreshold.incrementAndTest、metadata 恢复和onExecuteBootLoopMitigation,再说明高影响 rollback 为什么一次只执行一个。
