Skip to content

PMS 性能分析

从 PMS 快照、锁、安装批处理、异步 dexopt 和 PackageMetrics 源码分析性能边界。

基于android-17.0.0_r1
AndroidPMSPerformanceSnapshot

PMS 性能分析 ​

本文不把“性能优化”写成经验清单,而是回答三个源码问题:PMS 如何让高频查询避开全局写锁,安装为什么拆成锁内/锁外阶段,以及性能数据如何记录实际安装步骤。读者应先了解 PackageManagerShellCommand 和 PackageManager 实践。

Android 17 的 PMS 性能核心是读写分离:查询通过 Computer snapshot,写操作修改可观察状态并使 snapshot/cache 失效;安装则用 mInstallLock、批处理和 Future 把 I/O/dexopt 从主锁路径移开。

1. 读写模型 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:Snapshot

java
class Snapshot {
    static final int LIVE = 1;
    static final int SNAPPED = 2;

    public final Settings settings;
    public final WatchedArrayMap<String, AndroidPackage> packages;
    public final AppsFilterSnapshot appsFilter;
    public final ComponentResolverApi componentResolver;
    public final SharedLibrariesRead sharedLibraries;

    Snapshot(int type) {
        if (type == Snapshot.SNAPPED) {
            settings = mSettings.snapshot();
            packages = mPackagesSnapshot.snapshot();
            appsFilter = mAppsFilter.snapshot();
            componentResolver = mComponentResolver.snapshot();
            sharedLibraries = mSharedLibraries.snapshot();
        } else {
            settings = mSettings;
            packages = mPackages;
            appsFilter = mAppsFilter;
            componentResolver = mComponentResolver;
            sharedLibraries = mSharedLibraries;
        }
    }
}

Live computer 引用 PMS 可变对象,只在受控锁内使用;snapped computer 深拷贝 settings、包 map、resolver、AppsFilter 和 shared libraries,供并发查询。快照不是“缓存一份 PackageInfo”,而是把一组相互关联的读取状态固定在同一个版本。

2. snapshotComputer ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:snapshotComputer、rebuildSnapshot

java
public Computer snapshotComputer() {
    int pendingVersion =
            sSnapshotPendingVersion.get();
    Computer snapshot = sSnapshot.get();
    if (snapshot != null
            && snapshot.getVersion() == pendingVersion) {
        return snapshot.use();
    }

    synchronized (mSnapshotLock) {
        snapshot = sSnapshot.get();
        if (snapshot != null
                && snapshot.getVersion() == pendingVersion) {
            return snapshot.use();
        }
        Computer rebuilt = rebuildSnapshot(
                snapshot, pendingVersion);
        sSnapshot.set(rebuilt);
        return rebuilt.use();
    }
}

快照采用 double-check:先无锁读取 atomic snapshot,只有版本过期时才进入 mSnapshotLock 重建。多个 Binder 查询可以复用同一个版本;一次安装/状态写入只需推进 pending version,后续第一个读者承担重建成本。

3. 查询路径的成本 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java,符号:getPackageInfoInternal

java
public PackageInfo getPackageInfoInternal(
        String packageName, long versionCode,
        long flags, int filterCallingUid,
        int userId) {
    AndroidPackage pkg = mPackages.get(packageName);
    if (pkg == null) {
        pkg = resolvePackageName(packageName);
    }
    if (pkg == null) {
        return null;
    }
    PackageSetting ps =
            mSettings.getPackageLPr(
                    pkg.getPackageName());
    if (ps == null
            || filterSharedLibPackage(
                    ps, filterCallingUid, userId, flags)
            || shouldFilterApplication(
                    ps, filterCallingUid, userId)) {
        return null;
    }
    return generatePackageInfo(ps, flags, userId);
}

快照消除了大锁竞争,但并没有让查询 O(1) 无条件成立:仍有 package map 查找、重命名解析、共享库过滤、AppsFilter、用户状态读取和 PackageInfo 组装。调用方请求的 flags 越多,generatePackageInfo 要复制的组件、权限和 metadata 越多。

4. 写操作与失效 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:cache invalidation

java
public static void invalidatePackageInfoCache(
        int invalidationReason) {
    PackageManager.invalidatePackageInfoCache();
    onChanged();
    PackageMetrics.reportCacheInvalidationEvent(
            PackageMetrics
                    .CACHE_TYPE_APPLICATION_AND_PACKAGE_INFO,
            invalidationReason);
}

public static void invalidateGetPackagesForUidCache(
        int invalidationReason) {
    ApplicationPackageManager
            .invalidateGetPackagesForUidCache();
    PackageMetrics.reportCacheInvalidationEvent(
            PackageMetrics.CACHE_TYPE_GET_PACKAGES_FOR_UID,
            invalidationReason);
}

安装、删除、写 settings、权限 flag 变化和 AppsFilter 变化会让不同客户端缓存失效。缓存失效不是每次查询都重建所有对象;它先记录原因/推进版本,下一次需要时才生成新数据。

5. 安装锁与主锁 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:安装批处理

java
try (PackageManagerTracedLock installLock =
        mPm.mInstallLock.acquireLock()) {
    final int[] newUsers =
            getNewUsers(request, allUsers);
    mAppDataHelper.prepareAppDataPostCommitLIF(
            ps, 0, newUsers);
}

CompletableFuture<Void> future =
        mPm.getDexOptHelper()
                .performDexoptIfNeededAsync(request);
completableFutures.add(future);

安装锁保护 installd/app data 操作;dexopt 通过 Future 异步执行。PMS 主锁 mLock 用于包状态一致性,不能把文件 I/O、进程 stop/kill 或 dex2oat 长时间放在主锁中。

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:并发冻结

java
List<CompletableFuture<Void>> freezerFutures =
        new ArrayList<>();
for (ReconciledPackage pkg : reconciledPackages) {
    CompletableFuture<Void> future =
            CompletableFuture.runAsync(() ->
                    freezePackageForInstall(pkg));
    freezerFutures.add(future);
}
CompletableFuture.allOf(
        freezerFutures.toArray(
                new CompletableFuture[0]))
        .thenRun(() -> continueInstall());

多个包的冻结/停止可以并行等待,再回到受控锁内提交;这比在安装主线程逐包同步等待更容易放大 I/O 和进程生命周期延迟。

6. 安装耗时分段 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageMetrics.java,符号:步骤常量

java
public static final int STEP_PREPARE = 1;
public static final int STEP_SCAN = 2;
public static final int STEP_RECONCILE = 3;
public static final int STEP_COMMIT = 4;
public static final int STEP_DEXOPT = 5;
public static final int STEP_WAIT_DEXOPT = 8;
public static final int STEP_FREEZE_INSTALL = 6;
public static final int STEP_FREEZE_INSTALL_STOP_AND_KILL = 9;
public static final int STEP_RESTORE = 7;

PMS 把一次安装拆成 prepare、scan、reconcile、commit、dexopt、等待 dexopt、freeze、restore 等步骤。性能分析应先看哪一步增长,而不是只看总安装耗时。

java
public void onInstallSucceed() {
    reportInstallationStats(true /* success */);
}

private void reportInstallationStats(boolean success) {
    long duration = System.currentTimeMillis()
            - mInstallStartTimestampMillis;
    Pair<int[], long[]> durations =
            getInstallStepDurations();
    FrameworkStatsLog.write(
            FrameworkStatsLog
                    .PACKAGE_INSTALLATION_SESSION_REPORTED,
            mInstallRequest.getSessionId(),
            null, durations.first, durations.second);
}

PackageMetrics 的价值是把“慢”归因到阶段;没有这些分段,只能猜测是解析、锁、数据目录还是 dexopt。

7. 启动扫描 ​

PMS 启动扫描通过 InitAppsHelper 和 PackageParser2 扫描分区、APEX 和 data/app;扫描结果最终在受控锁内 reconcile/commit。不要用泛化的“线程数/CPU 核心数”推断实际收益,应以 trace section、扫描目录数量和 parse/commit 分段测量。

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java,符号:扫描入口

java
private void scanDirTracedLI(
        File scanDir, int parseFlags,
        int scanFlags, long currentTime,
        int seq) {
    Trace.traceBegin(
            Trace.TRACE_TAG_PACKAGE_MANAGER,
            "scanDir(" + scanDir + ")");
    try {
        // parse/scan packages under the partition
        scanDirLI(scanDir, parseFlags, scanFlags,
                currentTime, seq);
    } finally {
        Trace.traceEnd(
                Trace.TRACE_TAG_PACKAGE_MANAGER);
    }
}

真实设备上应将 trace 中 scanDir(...)、APEX scan、reconcile 和 settings 写入时间对应到代码阶段,不要把旧版本文章中的固定秒数当作 Android 17 的稳定基线。

8. 查询性能实践 ​

基于源码边界,调用方可遵循:

  1. 已知包名时使用单包 API,避免批量 PackageInfo 的 Parcel 和对象生成。
  2. 只请求需要的 flags;组件、权限、metadata 会扩大 generatePackageInfo 的工作量。
  3. 明确 userId;不要在业务层反复遍历 USER_ALL 再自行过滤。
  4. 不要在主线程循环调用高成本查询;客户端 Binder 仍会等待 system_server。
  5. 处理 NameNotFoundException/空结果,把包可见性和 user state 当作合法结果分支。

9. 失效与缓存观测 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageMetrics.java,符号:cache invalidation reason

java
public static final int
        INVALIDATION_REASON_INSTALL_PACKAGE = ...;
public static final int
        INVALIDATION_REASON_DELETE_PACKAGE = ...;
public static final int
        INVALIDATION_REASON_APP_FILTER_CHANGE = ...;
public static final int
        INVALIDATION_REASON_WRITE_SETTINGS = ...;
public static final int
        INVALIDATION_REASON_PERMISSION_FLAG_CHANGED = ...;

性能异常若表现为查询延迟突然升高或缓存频繁失效,应关联 invalidation reason 与最近发生的安装、删除、AppsFilter、settings 或权限变更,而不是简单增加缓存容量。

10. 性能定位表 ​

现象先看
查询偶发长尾snapshot rebuild、generatePackageInfo flags、AppsFilter
安装总耗时高PackageMetrics step durations
dexopt 阻塞安装STEP_DEXOPT/STEP_WAIT_DEXOPT、DexOptHelper Future
写操作导致查询抖动cache invalidation reason、snapshot version
启动慢scanDir(...)、APEX scan、reconcile、settings write trace
多包安装慢freeze futures、install lock contention、post-install restore

11. 阅读检查 ​

复述:查询 → snapshot version 命中/重建 → ComputerEngine 读取和过滤;安装 → prepare/scan/reconcile/commit → async dexopt/restore;状态变化 → cache invalidation reason → 下一次查询重建。然后回答:snapshot 是否让写操作无锁?flags 越多是否一定更快?安装总耗时只看 onInstallSucceed 是否足够定位?答案分别是“不,写入仍需锁”“不,可能扩大对象生成”“不,必须看分段指标”。