扫描性能分析
本文面向已经读过 扫描并行化、增量扫描 和 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.javaframeworks/base/services/core/java/com/android/server/pm/InitAppsHelper.javaframeworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.javaframeworks/base/services/core/java/com/android/server/pm/InitAppScanMetrics.java
| 观测 | owner | 记录对象 | 能回答的问题 |
|---|---|---|---|
| EventLog 启动标记 | PMS/InitAppsHelper | uptime 时间点 | system scan、data scan、PMS ready 的阶段边界 |
| system/data 汇总 | InitAppsHelper | 时间、包数、cache 数 | 哪一阶段总耗时高,平均到包是多少 |
| parser Trace | ParallelPackageParser/PackageParser2 | 每个文件区间 | parse 任务是否并行、是否有慢文件 |
| 单包 statsd atom | InitAppScanMetrics | updated 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
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
@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
@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
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
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
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
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
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
@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 启动时间线
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 采集
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
@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. 源码路线
定位扫描性能问题时,可以沿下面的顺序阅读:
PackageManagerService构造函数:确认 PMS EventLog、startTime、Settings 读取和 scan start/end/ready 边界。InitAppsHelper.logSystemAppsScanningTime()/logNonSystemAppScanningTime():确认 system/data wall-clock、packageCount、cache 差值和 OTA statsd。ParallelPackageParser:确认 parser Trace、队列/Future、异常和中断。InstallPackageHelper目录扫描与processParseResult():确认文件过滤、cache drop、parse/scan 错误和锁下注册。ScanPackageUtils.collectCertificatesLI():确认签名 cache hit/miss 和强制收集。ScanPackageUtils.scanPackageOnly()/PackageAbiHelperImpl:确认 ABI derive/reuse、APEX 和 shared user 条件。PackageCacher/preparePackageParserCache():确认 cache 根目录、key、mtime、path 和 feature flag 失效。InitAppScanMetrics:确认 atom 的字段、outcome 映射和只记录 updated system app 的条件。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 差值; ParallelPackageParserTrace 只覆盖 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 阶段。
