Skip to content

PackageWatchdog 健康监控

追踪 Android 17 包健康监控的失败分派、滑动窗口、启动循环检测与回滚消费者。

基于android-17.0.0_r1
AndroidPMSPackageWatchdogCrashRecoveryRollback

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。

java
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。

java
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 小时。

java
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。

java
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 属性控制。

java
@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. 源码练习 ​

  1. 从 notifyPackageFailure 追踪一次 app crash:指出 cooldown、long-task handler、MonitoredPackage.onFailureLocked、impact 比较和最终 observer 回调的顺序。
  2. 构造四次失败分散在两分钟内、五次失败集中在一分钟内两组输入,判断哪组会清空 failure history 并触发 observer。
  3. 对比 native crash 与 app crash:说明为什么前者跳过普通阈值,为什么 Rollback observer 不能依赖普通 VersionedPackage 找到失败包。
  4. 给定 system_server 在十分钟内反复重启,追踪 BootThreshold.incrementAndTest、metadata 恢复和 onExecuteBootLoopMitigation,再说明高影响 rollback 为什么一次只执行一个。