Skip to content

扫描性能分析

使用 Trace、EventLog、PackageCacher 计数和单包扫描指标定位 PMS 包扫描瓶颈。

基于android-17.0.0_r1
AndroidPackageManagerService包扫描性能TraceFrameworkStatsLog源码阅读

扫描性能分析 ​

本文面向已经读过 扫描并行化、增量扫描 和 packages.xml 恢复 的读者。前文解释了 parser 线程、启动状态、parser cache 和 OTA 收尾;本文把这些机制转换为可观测的性能证据,说明 Android 17 到底记录了哪些时间、计数和结果,以及如何从 Trace、EventLog、logcat 和 statsd 区分 parser 慢、锁等待、cache miss、签名收集慢和 commit 慢。

本文不提供未经设备实验支持的“平均开机提升百分比”,也不把一个总耗时数字当作瓶颈结论。AOSP 当前源码直接提供的是 system/data 阶段时间、包数量、cache 读取计数、每包扫描耗时、单个 updated system app 的总扫描时间和 outcome 分类;这些指标有明确 owner 和生效时机,但不能互相替代。

读完后,读者应能定位 BOOT_PROGRESS_PMS_SYSTEM_SCAN_START、BOOT_PROGRESS_PMS_DATA_SCAN_START、BOOT_PROGRESS_PMS_SCAN_END 和 BOOT_PROGRESS_PMS_READY 的时间边界;解释 InitAppsHelper 如何计算 system/data 平均耗时和 cache 数;从 InitAppScanMetrics 读出单个包的总时长、split 数、签名方案和失败 outcome;还能根据 Trace 与日志判断瓶颈发生在目录枚举、并行 parse、证书收集、扫描注册、AppData 清理还是锁下提交。

1. 指标边界 ​

1.1 四类观测 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
  • frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
  • frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
  • frameworks/base/services/core/java/com/android/server/pm/InitAppScanMetrics.java
观测owner记录对象能回答的问题
EventLog 启动标记PMS/InitAppsHelperuptime 时间点system scan、data scan、PMS ready 的阶段边界
system/data 汇总InitAppsHelper时间、包数、cache 数哪一阶段总耗时高,平均到包是多少
parser TraceParallelPackageParser/PackageParser2每个文件区间parse 任务是否并行、是否有慢文件
单包 statsd atomInitAppScanMetricsupdated system app 的总扫描哪个包、split、签名方案或错误 outcome 异常

总耗时是 wall-clock;平均耗时是阶段总时长除以最终包数量;cache 计数来自静态累计计数器;单包 metrics 只在特定初始化路径和条件下记录。分析前先确认指标覆盖范围,否则会把不同时间口径相加。

1.2 不能直接相加 ​

mSystemScanTime 覆盖 APEX/system 处理、system overlay/framework/app/priv-app 注册和收尾;mDataScanTime 是从总体 startTime 中扣除 system time 后得到的 data 阶段 wall-clock。并行 parse 的线程 CPU 时间不能直接与 wall-clock 相加;InitAppScanMetrics 的单包总时长也可能包含锁等待和注册,而非纯 XML parse。

2. 阶段计时 ​

2.1 PMS 标记 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java、frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java

java
long startTime = SystemClock.uptimeMillis();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, startTime);

PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,
        startTime);
mInitAppsHelper.initNonSystemApps(packageParser, userIds, startTime);
packageParser.close();

EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
        SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
        + ((SystemClock.uptimeMillis() - startTime) / 1000f) + " seconds");

system scan 和 data scan 共用同一个 startTime,因此 data 阶段不能只看一个独立 stopwatch;InitAppsHelper.logNonSystemAppScanningTime() 会减去 system time。BOOT_PROGRESS_PMS_READY 在后续 Settings 写回后记录,故 PMS scan end 到 ready 之间仍可能有权限、AppData、resolver 和 metadata 工作。

2.2 system 汇总 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java

java
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void logSystemAppsScanningTime(long startTime) {
    mCachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
    mPm.mSettings.pruneSharedUsersLPw();
    mSystemScanTime = SystemClock.uptimeMillis() - startTime;
    mSystemPackagesCount = mPm.mPackages.size();
    Slog.i(TAG, "Finished scanning system apps. Time: " + mSystemScanTime
            + " ms, packageCount: " + mSystemPackagesCount
            + " , timePerPackage: "
            + (mSystemPackagesCount == 0 ? 0 : mSystemScanTime / mSystemPackagesCount)
            + " , cached: " + mCachedSystemApps);
    if (mIsDeviceUpgrading && mSystemPackagesCount > 0) {
        FrameworkStatsLog.write(
                FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
                BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME,
                mSystemScanTime / mSystemPackagesCount);
    }
}

system 汇总在 overlay config 初始化、stub 列表更新和 system package cleanup 准备之后执行。packageCount 是当前 mPm.mPackages.size(),不是目录中的 APK 数;被跳过、隐藏或失败的包可能不在计数中。OTA 时才上报 system average scan time 到 BOOT_TIME_EVENT_DURATION_REPORTED。

2.3 data 汇总 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java

java
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void logNonSystemAppScanningTime(long startTime) {
    final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
            - mCachedSystemApps;
    final long dataScanTime = SystemClock.uptimeMillis() - mSystemScanTime - startTime;
    final int dataPackagesCount = mPm.mPackages.size() - mSystemPackagesCount;
    Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
            + " ms, packageCount: " + dataPackagesCount
            + " , timePerPackage: "
            + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
            + " , cached: " + cachedNonSystemApps);
    if (mIsDeviceUpgrading && dataPackagesCount > 0) {
        FrameworkStatsLog.write(
                FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
                BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME,
                dataScanTime / dataPackagesCount);
    }
}

data cache 数是静态累计值减去 system 快照;如果其他路径在两次快照之间读取了 parser cache,简单相减就不能解释为严格的 data 命中数。data time 也包含 fixSystemPackages()、executor 关闭和 renamed package prune 前后的阶段边界,不能当成纯 /data/app parse 时间。

3. 单包统计 ​

3.1 单包指标 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppScanMetrics.java

java
public final class InitAppScanMetrics {
    private String mPackageName;
    private boolean mIsFsiEnabled;
    private int mNumApkSplits;
    private int mSignatureSchemeVersion =
            FrameworkStatsLog.INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__UNKNOWN;
    private final long mTotalScanStartTimeMillis;
    private long mTotalScanDurationMillis;
    private int mInitAppScanOutcome =
            FrameworkStatsLog.INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__UNSPECIFIED;

    public InitAppScanMetrics() {
        this.mTotalScanStartTimeMillis = SystemClock.uptimeMillis();
    }
}

Android 17 的类只有总扫描起止时间、package name、FSI allowlist 状态、split 数、签名 scheme 和 outcome;archive 中常见的 parse/certificate/scan 三段耗时字段并不在当前版本类中。不能把旧字段或旧 atom 参数表迁移到 Android 17。

3.2 outcome 映射 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppScanMetrics.java

java
private static int translateToInitAppScanOutcome(int returnCode) {
    switch (returnCode) {
        case PackageManager.INSTALL_SUCCEEDED:
            return FrameworkStatsLog.INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__SUCCESS;
        case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES:
            return FrameworkStatsLog
                    .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_NO_CERTIFICATES;
        case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE:
            return FrameworkStatsLog
                    .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_VERIFICATION;
        case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE:
            return FrameworkStatsLog
                    .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_UPDATE_INCOMPATIBLE;
        case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:
            return FrameworkStatsLog
                    .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_INCONSISTENT_CERTIFICATES;
        case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING:
            return FrameworkStatsLog
                    .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_CERTIFICATE_ENCODING;
        case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE:
        case PackageManager.INSTALL_FAILED_INVALID_APK:
        case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED:
        case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION:
            return FrameworkStatsLog
                    .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_SCAN_VALIDATION;
        default:
            return FrameworkStatsLog
                    .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_OTHER;
    }
}

outcome 是安装错误码到 statsd 枚举的压缩映射:证书缺失、验证失败、更新不兼容、证书不一致、编码错误和 scan validation 有独立类别,其余错误进入 FAILURE_OTHER。它不区分 parse 函数内部每个子步骤,也不包含“缓存命中” outcome。

3.3 上报时机 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java、frameworks/base/services/core/java/com/android/server/pm/InitAppScanMetrics.java

java
final InitAppScanMetrics metrics = new InitAppScanMetrics();
boolean shouldLogInitAppScanMetric = false;
try {
    final boolean scanSystemPartition =
            (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
    final boolean isSystemPkgUpdated = disabledPkgSetting != null;
    shouldLogInitAppScanMetric = !scanSystemPartition && isSystemPkgUpdated;

    metrics.setIsFsiEnabled(forceCollect)
            .setPackageName(parsedPackage.getPackageName())
            .setNumApkSplits(parsedPackage.getSplitCodePaths() == null
                    ? 0 : parsedPackage.getSplitCodePaths().length)
            .setSignatureSchemeVersion(
                    parsedPackage.getSigningDetails().getSignatureSchemeVersion());

    metrics.setInitAppScanOutcome(PackageManager.INSTALL_SUCCEEDED);
} catch (PackageManagerException e) {
    metrics.setInitAppScanOutcome(e.error);
    throw e;
} finally {
    if (shouldLogInitAppScanMetric) {
        metrics.log();
    }
}

当前源码的单包 metrics 只在“非 system partition 且存在 updated system package”时记录,主要观察 data 上更新的 system app 初始化扫描。普通新安装、纯 system factory package 和很多 data app 不会产生同一 atom;没有 atom 不能推断该包没有扫描耗时。

4. Trace 分解 ​

4.1 parser 区段 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ParallelPackageParser.java

java
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
        "parallel parsePackage [" + scanFile + "]");
try {
    pr.scanFile = scanFile;
    pr.scanParams = scanParams;
    pr.parsedPackage = parsePackage(scanFile, scanParams.parseFlags);
} catch (Throwable e) {
    pr.throwable = e;
} finally {
    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}

Trace 区段覆盖 PackageParser2.parsePackage() 和异常包装,不覆盖结果入队阻塞、processParseResult()、签名兼容性和 commit。多个区段重叠说明 parser 任务并行,不说明主线程消费没有等待。

4.2 证书与 ABI ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java、frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
ScanPackageUtils.collectCertificatesLI(pkgSetting, parsedPackage,
        mPm.getSettingsVersionForPackage(parsedPackage), forceCollect, skipVerify,
        mPm.isPreNMR1Upgrade());
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
        packageAbiHelper.derivePackageAbi(parsedPackage, isSystemApp,
                isUpdatedSystemApp, cpuAbiOverride, appLib32InstallDir);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

证书 Trace 只在 collectCertificatesLI() 被重新调用时出现;命中 PackageSetting 签名缓存会提前返回。ABI Trace 只在 first boot/upgrade、stub 或缺少旧设置等条件下出现;普通启动复用设置时不会出现同样的 derive 区段。应结合 scan flags 和 cache 日志解释 Trace 缺失。

4.3 锁下注册 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void processParseResult(ParallelPackageParser.ParseResult result) {
    if (result.throwable == null) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "addForInitLI");
        try {
            addForInitLI(result.parsedPackage, result.scanParams.parseFlags,
                    result.scanParams.scanFlags,
                    new UserHandle(UserHandle.USER_SYSTEM), result.scanParams.apexInfo);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }
}

addForInitLI()、Settings、reconcile、AppId 和组件注册都位于受锁保护的消费路径。若 parser 区段很短但 addForInitLI 很长,瓶颈在扫描/注册或锁竞争;若 parser 区段重叠但整体 system time 不降,可能是消费顺序、I/O、锁或后续 cleanup 限制了收益。

5. 诊断方法 ​

5.1 启动时间线 ​

text
adb shell logcat -b events -s boot_progress_pms
adb shell logcat -s PackageManager PackageManagerService InitAppsHelper PackageCacher
adb shell dumpsys package version

先用 EventLog 标出 system scan start、data scan start、scan end 和 ready,再用 Finished scanning system apps/Finished scanning non-system apps 日志核对 packageCount、timePerPackage 和 cached。时间线只回答“哪一段长”,不能回答“哪一个 APK 长”;后者需要 Trace 或单包 metrics。

5.2 Trace 采集 ​

text
adb shell atrace --async_start package_manager sched freq idle
adb shell atrace --async_stop -z -o /data/local/tmp/pms-scan.html

在 trace 中搜索:parsePackage、parallel parsePackage、collectCertificates、derivePackageAbi、addForInitLI、scanDir、parallelScanDir。观察 parser 区段是否重叠、首个 Future.get() 是否长时间等待、addForInitLI 是否串行占据主线程,以及 OTA code cache 清理是否落在 PMS scan end 之后。

5.3 cache 命中 ​

PackageCacher.sCachedPackageReadCount 是进程内 AtomicInteger,system 汇总取一次,data 汇总用差值。它不是持久化 counter,也不是命中率;要计算近似命中率,至少还需要同一阶段提交的 package 数,并注意失败/隐藏包不在 mPackages.size() 中。

preparePackageParserCache() 日志中的旧 fingerprint 目录删除、PackageCacher.getCachedResult() 的 mtime/path/feature flag 失效和 SCAN_DROP_CACHE 的逐文件清理,应分别记录。只看到 cache 目录存在,不能证明某个 APK 命中。

5.4 statsd 读取 ​

InitAppScanMetrics.log() 写入 INIT_APP_SCAN_REPORTED,字段是 FSI 状态、split 数、signature scheme、总时长、outcome 和 package name。该 atom 适合找 updated system app 的异常包,不适合作为所有 APK 的完整分布。若需要 system/data 总体趋势,应使用 BOOT_TIME_EVENT_DURATION_REPORTED 的 OTA package manager 事件和启动日志。

6. 测试与边界 ​

6.1 VersionInfo 测试 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java

java
@Test
public void testReadWriteSettingsVersionCodeWithSdkVersionFull() {
    final Settings settingsUnderTest = makeSettings();
    final String uuid = UUID.randomUUID().toString();
    Settings.VersionInfo versionInfo = settingsUnderTest.findOrCreateVersion(uuid);
    versionInfo.forceCurrent();
    settingsUnderTest.writeLPr(computer, /*sync=*/ true);
    settingsUnderTest.onVolumeForgotten(uuid);

    assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true));
    Settings.VersionInfo readVersionInfo = settingsUnderTest.findOrCreateVersion(uuid);
    assertThat(readVersionInfo.sdkVersionFull, is(Build.VERSION.SDK_INT_FULL));
    assertThat(readVersionInfo.sdkVersion, is(Build.VERSION.SDK_INT));
}

这个测试证明 VersionInfo 写入/读取和 SDK full version 一致性,支持解释升级状态的持久化来源;它没有测量 fingerprint 变化时的真实启动耗时,也没有测量 parser cache。

6.2 scan 结果测试 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java

ScanTests 中的 scanFirstBoot_derivesAbis()、scanFirstBoot_apexDontDeriveAbis() 和 executeScan() 能证明 first boot/APEX flags 对 ABI 派生分支的影响;newInstallSimpleAllNominal()、updateSimpleNominal() 能证明新设置/更新副本的结果形态。这些测试是功能边界,不是性能基准:mock ABI helper 和直接调用 scanPackageOnly() 都不会代表真实磁盘 I/O、线程调度或 statsd 写入成本。

6.3 指标缺口 ​

Android 17 当前 InitAppScanMetrics 没有单独 parse/certificate/scan duration 字段,也没有通用“缓存跳过” outcome。要区分这些阶段,必须把 Trace、PackageCacher 日志、collectCertificatesLI()/derivePackageAbi 条件和阶段汇总结合起来。不能从单个 atom 反推出完整 CPU profile。

7. 源码路线 ​

定位扫描性能问题时,可以沿下面的顺序阅读:

  1. PackageManagerService 构造函数:确认 PMS EventLog、startTime、Settings 读取和 scan start/end/ready 边界。
  2. InitAppsHelper.logSystemAppsScanningTime()/logNonSystemAppScanningTime():确认 system/data wall-clock、packageCount、cache 差值和 OTA statsd。
  3. ParallelPackageParser:确认 parser Trace、队列/Future、异常和中断。
  4. InstallPackageHelper 目录扫描与 processParseResult():确认文件过滤、cache drop、parse/scan 错误和锁下注册。
  5. ScanPackageUtils.collectCertificatesLI():确认签名 cache hit/miss 和强制收集。
  6. ScanPackageUtils.scanPackageOnly()/PackageAbiHelperImpl:确认 ABI derive/reuse、APEX 和 shared user 条件。
  7. PackageCacher/preparePackageParserCache():确认 cache 根目录、key、mtime、path 和 feature flag 失效。
  8. InitAppScanMetrics:确认 atom 的字段、outcome 映射和只记录 updated system app 的条件。
  9. AppDataHelper、fixSystemPackages() 和 Settings 写回:确认 scan time 之外的收尾开销。

一个实用练习是:system scan 日志显示 800 ms、200 个包、cache=190;Trace 中 parser 区段大量重叠,但 addForInitLI 串行等待 1.5 s;data scan 只有 10 个包却耗时 2 s。请指出平均值为何会误导、应该先看哪些 Trace/锁/清理路径、cache=190 能否直接计算 95% 命中率,以及哪些结论需要设备实验才能成立。

8. 设计收束 ​

Android 17 的 PMS 扫描性能分析必须把观测口径和源码 owner 对齐:

  • EventLog 定义 PMS 阶段边界,InitAppsHelper 计算 system/data wall-clock、包数和 cache 差值;
  • ParallelPackageParser Trace 只覆盖 parser,不覆盖队列等待、注册和 commit;
  • InitAppScanMetrics 当前只记录特定 updated system app 的总时长、split、签名 scheme、FSI 和 outcome,没有旧版常见的分段时长字段;
  • parser cache 由 fingerprint 目录、flags/path key、mtime、APEX backing file 和 feature flag 共同决定,存在 cache 目录不等于命中;
  • OTA 额外触发权限重授、code cache 清理、ART profile 保留和 statsd 事件,这些耗时不能都归因于 APK parser;
  • 真正的瓶颈结论需要把阶段日志、Trace 重叠/等待、cache 条件、锁下注册、AppData 清理和测试/设备实验拼起来。

后续解析专题将进入 PackageParser2,继续追踪 parser callback、cache 接口和 ParsedPackage 构造,解释扫描性能数据最终对应哪些解析对象和 Manifest 阶段。