Skip to content

dumpsys package

从 PMS dump 入口追踪 DumpState、DumpHelper、Computer 与 Settings 的输出调度和诊断方法。

基于android-17.0.0_r1
AndroidPackageManagerServicedumpsysDumpState源码阅读

dumpsys package ​

dumpsys package 的输出不是一段由 PMS 主类随意拼接的日志,而是一条有明确调度层次的诊断路径:Binder dump() 入口先验证 dump 权限并取得 Computer snapshot,然后 DumpHelper 把命令行参数转换为 DumpState 位图,最后按 section 将工作交给 Computer、Settings、ComponentResolver、PermissionManager、PackageInstaller、Domain Verification、StorageEvent 和 APEX owner。

这篇文章的目标是让读者能从源码解释一段 dump 输出,而不是记住几个命令参数。具体会回答:

  • dumpsys package 经过哪一个 Binder service,为什么 dump 使用 snapshot;
  • DumpState.mTypes 如何控制 section,为什么默认输出不包含 preferred XML;
  • DumpHelper 如何识别单包、check-permission、resolver、packages、queries、apex 和 snapshot statistics;
  • --checkin 与普通文本输出为什么走不同分支;
  • packages、shared-users、queries、dexopt、apex 等输出分别由谁拥有;
  • 单包 dump、未知包、权限错误和 XML/proto 写入失败如何表现;
  • 如何用真实字段反推 PackageSetting、per-user state、AppsFilter、冻结包和 snapshot 统计;
  • 现有测试与手工 dump 各自能证明什么,不能证明什么。

PMS004、PMS005、PMS009、PMS010 和 PMS014 已分别讲过 Settings、PackageState、Computer、快照和 native 接口;本文把它们作为 dump 的消费者重新串起来,不重复各自的内部算法。

1. dump入口 ​

1.1 权限与snapshot ​

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

java
@SuppressWarnings("resource")
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;

    final Computer snapshot = snapshotComputer();
    final KnownPackages knownPackages = new KnownPackages(
            mDefaultAppProvider,
            mRequiredInstallerPackage,
            mRequiredUninstallerPackage,
            mSetupWizardPackage,
            mRequiredVerifierPackages,
            mDefaultTextClassifierPackage,
            mSystemTextClassifierPackageName,
            mRequiredPermissionControllerPackage,
            mConfiguratorPackage,
            mIncidentReportApproverPackage,
            mAmbientContextDetectionPackage,
            mWearableSensingPackage,
            mAppPredictionServicePackage,
            COMPANION_PACKAGE_NAME,
            mRetailDemoPackage,
            mOverlayConfigSignaturePackage,
            mRecentsPackage,
            mDeveloperVerificationServiceProvider == null
                    ? null : mDeveloperVerificationServiceProvider.getPackageName());

    final ArrayMap<String, FeatureInfo> availableFeatures = new ArrayMap<>(mAvailableFeatures);
    final ArraySet<String> protectedBroadcasts;
    synchronized (mProtectedBroadcasts) {
        protectedBroadcasts = new ArraySet<>(mProtectedBroadcasts);
    }

    new DumpHelper(mPermissionManager, mStorageEventHelper,
            mDomainVerificationManager, mInstallerService, mRequiredVerifierPackages,
            knownPackages, mChangedPackagesTracker, availableFeatures, protectedBroadcasts,
            getPerUidReadTimeouts(snapshot), mSnapshotStatistics
    ).doDump(snapshot, fd, pw, args);
}

入口的顺序有实际含义:

  1. DumpUtils.checkDumpAndUsageStatsPermission() 失败时直接返回,后续不会泄露任何 dump 内容;
  2. snapshotComputer() 为包、组件和可见性相关 section 提供统一查询视图;
  3. KnownPackages、features、protected broadcasts 被复制或在局部锁内复制,避免 DumpHelper 遍历 PMS live 集合;
  4. DumpHelper 只接收已经准备好的依赖和 snapshot,不直接读取 PMS 的全部字段。

dump() 返回并不表示所有内容都来自同一个深拷贝:mAvailableFeatures 和 mProtectedBroadcasts 在入口处复制,mPermissionManager、mStorageEventHelper 等仍是外部 owner 引用。PMS010 所说的 snapshot 边界在 dump 中同样成立。

1.2 DumpHelper依赖 ​

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

java
final class DumpHelper {
    private final PermissionManagerServiceInternal mPermissionManager;
    private final StorageEventHelper mStorageEventHelper;
    private final DomainVerificationManagerInternal mDomainVerificationManager;
    private final PackageInstallerService mInstallerService;
    private final String[] mRequiredVerifierPackages;
    private final KnownPackages mKnownPackages;
    private final ChangedPackagesTracker mChangedPackagesTracker;
    private final ArrayMap<String, FeatureInfo> mAvailableFeatures;
    private final ArraySet<String> mProtectedBroadcasts;
    private final PerUidReadTimeouts[] mPerUidReadTimeouts;
    private final SnapshotStatistics mSnapshotStatistics;

这些字段对应 dump 的 owner,而不是输出 section 的简单名字:

owner典型 section
Computer snapshotversion、libraries、preferred、queries、dexopt、frozen、APEX、消息等查询型 section
Settings(通过 Computer 或内部状态)packages、shared users、preferred XML、读取消息
PermissionManagerServiceInternalpermissions、check-permission
ComponentResolverApiActivity/Service/Receiver/Provider resolver tables
PackageInstallerServiceinstalls/session 状态
DomainVerificationManagerInternaldomain verifier/preferred 状态
StorageEventHelpervolumes
KnownPackages/PMS copiesknown packages、features、protected broadcasts
SnapshotStatisticssnapshot statistics

当某个 section 输出异常时,应先定位 owner,再判断 snapshot、锁或持久化问题;不要只在 PackageManagerService.dump() 入口处加日志。

2. DumpState ​

2.1 section 位图 ​

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

java
public final class DumpState {
    public static final int DUMP_LIBS = 1 << 0;
    public static final int DUMP_FEATURES = 1 << 1;
    public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
    public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
    public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
    public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
    public static final int DUMP_PERMISSIONS = 1 << 6;
    public static final int DUMP_PACKAGES = 1 << 7;
    public static final int DUMP_SHARED_USERS = 1 << 8;
    public static final int DUMP_MESSAGES = 1 << 9;
    public static final int DUMP_PROVIDERS = 1 << 10;
    public static final int DUMP_VERIFIERS = 1 << 11;
    public static final int DUMP_PREFERRED = 1 << 12;
    public static final int DUMP_PREFERRED_XML = 1 << 13;
    public static final int DUMP_KEYSETS = 1 << 14;
    public static final int DUMP_VERSION = 1 << 15;
    public static final int DUMP_INSTALLS = 1 << 16;
    public static final int DUMP_DOMAIN_VERIFIER = 1 << 17;
    public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
    public static final int DUMP_FROZEN = 1 << 19;
    public static final int DUMP_DEXOPT = 1 << 20;
    public static final int DUMP_CHANGES = 1 << 22;
    public static final int DUMP_VOLUMES = 1 << 23;
    public static final int DUMP_SERVICE_PERMISSIONS = 1 << 24;
    public static final int DUMP_APEX = 1 << 25;
    public static final int DUMP_QUERIES = 1 << 26;
    public static final int DUMP_KNOWN_PACKAGES = 1 << 27;
    public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28;
    public static final int DUMP_SNAPSHOT_STATISTICS = 1 << 29;
    public static final int DUMP_PROTECTED_BROADCASTS = 1 << 30;

    public static final int OPTION_SHOW_FILTERS = 1 << 0;
    public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1;
    public static final int OPTION_SKIP_PERMISSIONS = 1 << 2;
    public static final int OPTION_INCLUDE_APEX = 1 << 3;

section 使用 bitmask,允许一个命令同时选择多个输出。例如 permission CAMERA com.example 会同时设置 permissions、packages、shared users 三个位;resolvers activity service 会设置两个 resolver 位。

2.2 默认与标题 ​

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

java
private int mTypes;
private int mOptions;
private boolean mTitlePrinted;
private boolean mFullPreferred;
private boolean mCheckIn;
private boolean mBrief;
private String mTargetPackageName;
private SharedUserSetting mSharedUser;

public boolean isDumping(int type) {
    if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
        return true;
    }
    return (mTypes & type) != 0;
}

public void setDump(int type) {
    mTypes |= type;
}

public boolean onTitlePrinted() {
    final boolean printed = mTitlePrinted;
    mTitlePrinted = true;
    return printed;
}

当 mTypes == 0 时,isDumping() 默认打开除 DUMP_PREFERRED_XML 外的所有 section。preferred XML 被排除是因为它可能很大,必须显式使用 preferred-xml。mTitlePrinted 让多个 owner 共享标题间的空行状态:第一个 section 打印标题,后续 section 在标题前插入空行,而不是各自重复初始化输出格式。

2.3 单包过滤 ​

DumpState 还保存两个过滤上下文:

java
public String getTargetPackageName() {
    return mTargetPackageName;
}

public void setTargetPackageName(String packageName) {
    mTargetPackageName = packageName;
}

public SharedUserSetting getSharedUser() {
    return mSharedUser;
}

public void setSharedUser(SharedUserSetting user) {
    mSharedUser = user;
}

targetPackageName 由 DumpHelper 设置,传给 Computer、Settings 和 resolver,让各 owner 只输出目标包;sharedUser 则在 Settings 遍历 package 时,根据目标包所属 shared UID 设置,随后 dumpSharedUsersLPr() 只打印对应 group。

3. 参数解析 ​

3.1 全局选项 ​

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

java
int opti = 0;
while (opti < args.length) {
    String opt = args[opti];
    if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
        break;
    }
    opti++;

    if ("-a".equals(opt)) {
        // Right now we only know how to print all.
    } else if ("-h".equals(opt)) {
        printHelp(pw);
        return;
    } else if ("--checkin".equals(opt)) {
        dumpState.setCheckIn(true);
    } else if ("--all-components".equals(opt)) {
        dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS);
    } else if ("-f".equals(opt)) {
        dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
    } else if ("--include-apex".equals(opt)) {
        dumpState.setOptionEnabled(DumpState.OPTION_INCLUDE_APEX);
    } else if ("--proto".equals(opt)) {
        dumpProto(snapshot, fd);
        return;
    } else {
        pw.println("Unknown argument: " + opt + "; use -h for help");
    }
}

选项解析在 section 解析前完成。--proto 是立即返回分支,不会继续执行文本 section;-h 同样打印帮助并返回。未知选项只打印一行错误,随后仍可能继续解析剩余参数,这一点决定了命令行错误不一定阻止后续 dump。

3.2 单包识别 ​

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

java
if ("android".equals(cmd) || cmd.contains(".")) {
    dumpState.setTargetPackageName(cmd);
    // When dumping a single package, we always dump all of its
    // filter information since the amount of data will be reasonable.
    dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
}

第一个非选项参数如果是 android 或包含点号,就被当作 package name,而不是 section。单包 dump 自动开启 OPTION_SHOW_FILTERS,所以 dumpsys package com.example.app 会比全量 package dump 展示更多组件 filter 细节。

3.3 check-permission ​

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

java
} else if ("check-permission".equals(cmd)) {
    if (opti >= args.length) {
        pw.println("Error: check-permission missing permission argument");
        return;
    }
    String perm = args[opti++];
    if (opti >= args.length) {
        pw.println("Error: check-permission missing package argument");
        return;
    }

    String pkg = args[opti++];
    int user = UserHandle.getUserId(Binder.getCallingUid());
    if (opti < args.length) {
        try {
            user = Integer.parseInt(args[opti]);
        } catch (NumberFormatException e) {
            pw.println("Error: check-permission user argument is not a number: "
                    + args[opti]);
            return;
        }
    }

    // Normalize package name to handle renamed packages and static libs
    pkg = snapshot.resolveInternalPackageName(pkg, PackageManager.VERSION_CODE_HIGHEST);
    pw.println(mPermissionManager.checkPermission(
            pkg, perm, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, user));
    return;
}

这个命令不是设置 DUMP_PERMISSIONS 后继续走统一输出,而是解析完参数后直接打印 permission check 结果并返回。user 默认取 dump 调用者 user;如果显式传入 user,字符串必须能解析为整数;包名先经过 Computer 的重命名/static library 规范化,再交给 PermissionManager。它适合验证一个具体权限结果,不适合查看完整权限定义。

3.4 别名与组合 ​

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

java
} else if ("l".equals(cmd) || "libraries".equals(cmd)) {
    dumpState.setDump(DumpState.DUMP_LIBS);
} else if ("f".equals(cmd) || "features".equals(cmd)) {
    dumpState.setDump(DumpState.DUMP_FEATURES);
} else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
    if (opti >= args.length) {
        dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
                | DumpState.DUMP_SERVICE_RESOLVERS
                | DumpState.DUMP_RECEIVER_RESOLVERS
                | DumpState.DUMP_CONTENT_RESOLVERS);
    } else {
        while (opti < args.length) {
            String name = args[opti++];
            if ("a".equals(name) || "activity".equals(name)) {
                dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
            } else if ("s".equals(name) || "service".equals(name)) {
                dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
            } else if ("r".equals(name) || "receiver".equals(name)) {
                dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
            } else if ("c".equals(name) || "content".equals(name)) {
                dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
            } else {
                pw.println("Error: unknown resolver table type: " + name);
                return;
            }
        }
    }
}

r/resolvers 没有子类型时打开四张 resolver 表;有子类型时把后续 token 都当作 resolver kind。命令 resolvers activity service 的参数消费范围与普通 section 不同,阅读其他参数时必须注意 opti 是否已经被子循环推进。

3.5 常用section ​

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

java
} else if ("p".equals(cmd) || "packages".equals(cmd)) {
    dumpState.setDump(DumpState.DUMP_PACKAGES);
} else if ("q".equals(cmd) || "queries".equals(cmd)) {
    dumpState.setDump(DumpState.DUMP_QUERIES);
} else if ("snapshot".equals(cmd)) {
    dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
    if (opti < args.length) {
        if ("--full".equals(args[opti])) {
            dumpState.setBrief(false);
            opti++;
        } else if ("--brief".equals(args[opti])) {
            dumpState.setBrief(true);
            opti++;
        }
    }
} else if ("apex".equals(cmd)) {
    if (opti < args.length) {
        dumpState.setTargetPackageName(args[opti++]);
    }
    dumpState.setDump(DumpState.DUMP_APEX);
}

snapshot --full/--brief 只影响 SnapshotStatistics.dump() 的输出详细程度;apex 后可选 package name,设置后既影响 DumpHelper 的 package guard,也影响 Computer 的 APEX section 过滤。

3.6 帮助文本 ​

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

java
private void printHelp(PrintWriter pw) {
    pw.println("Package manager dump options:");
    pw.println("  [-h] [-f] [--checkin] [--all-components] [cmd] ...");
    pw.println("    --checkin: dump for a checkin");
    pw.println("    -f: print details of intent filters");
    pw.println("    -h: print this help");
    pw.println("    --proto: dump data to proto");
    pw.println("    --all-components: include all component names in package dump");
    pw.println("    --include-apex: includes the apex packages in package dump");
    pw.println("  cmd may be one of:");
    pw.println("    apex: list active APEXes and APEX session state");
    pw.println("    l[ibraries]: list known shared libraries");
    pw.println("    f[eatures]: list device features");
    pw.println("    k[eysets]: print known keysets");
    pw.println("    r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
}

帮助文本列出的是当前实现支持的命令语法,不等于每个命令都输出完整系统状态。比如 apex 走 Computer 的 APEX dump,features 使用 DumpHelper 持有的 feature copy,packages 走 Settings package dump;同样的 shell 前缀对应不同 owner。

4. 调度顺序 ​

4.1 存在性guard ​

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

java
final String packageName = dumpState.getTargetPackageName();
final boolean checkin = dumpState.isCheckIn();

// Return if the package doesn't exist.
if (packageName != null
        && snapshot.getPackageStateInternal(packageName) == null
        && !snapshot.isApexPackage(packageName)) {
    pw.println("Unable to find package: " + packageName);
    return;
}

if (checkin) {
    pw.println("vers,1");
}

单包 dump 在所有 section 前做一次 package state guard。APEX package 即使不在普通 package state 查找结果中,也允许继续进入 APEX dump。未知普通包直接输出 Unable to find package,不会输出部分 packages/resolver 内容。

4.2 固定输出顺序 ​

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

java
if (!checkin && dumpState.isDumping(DumpState.DUMP_VERSION)
        && packageName == null) {
    snapshot.dump(DumpState.DUMP_VERSION, fd, pw, dumpState);
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES)
        && packageName == null) {
    // Known Packages section
}
if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
    // Verifiers section
}
if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER)
        && packageName == null) {
    // Domain Verifier section
}
if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
    snapshot.dump(DumpState.DUMP_LIBS, fd, pw, dumpState);
}
if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) {
    // Features section
}

后续依次是 resolver tables、preferred、preferred XML、domain preferred、permissions、providers、keysets、packages、queries、shared users、changes、volumes、service permissions、installs、frozen、APEX、per-UID timeouts、snapshot statistics、protected broadcasts。输出顺序由这些 if 的源码顺序决定;调试脚本不能假设 section 按用户命令顺序输出。

4.3 resolver owner ​

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

java
final ComponentResolverApi componentResolver = snapshot.getComponentResolver();
if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
    componentResolver.dumpActivityResolvers(pw, dumpState, packageName);
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
    componentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
    componentResolver.dumpServiceResolvers(pw, dumpState, packageName);
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
    componentResolver.dumpProviderResolvers(pw, dumpState, packageName);
}

resolver 输出只在非 checkin 模式执行。-f 通过 DumpState.OPTION_SHOW_FILTERS 传给 resolver,最终由 ComponentResolverBase 决定是否打印 IntentFilter 细节。单包 dump 自动开启这个 option,所以它的 resolver 输出比全量文本更适合检查某个包的 filter。

4.4 包与shared UID ​

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

java
if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
    snapshot.dumpPackages(pw, packageName, permissionNames, dumpState, checkin);
}

if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
    snapshot.dumpSharedUsers(pw, packageName, permissionNames, dumpState, checkin);
}

这两个 section 即使在 checkin 模式也可能执行;具体输出格式由 Settings.dumpPackagesLPr() 和 dumpSharedUsersLPr() 依据 checkin 参数决定。permission <name> 命令同时打开 packages/shared users,并通过 permissionNames 让 Settings 只保留拥有相关权限状态的条目。

4.5 统计section ​

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

java
if (!checkin
        && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)
        && packageName == null) {
    if (dumpState.onTitlePrinted()) {
        pw.println();
    }
    pw.println("Snapshot statistics:");
    mSnapshotStatistics.dump(pw, "   " /* indent */, SystemClock.currentTimeMicro(),
            snapshot.getUsed(), dumpState.isBrief());
}

if (!checkin
        && dumpState.isDumping(DumpState.DUMP_PROTECTED_BROADCASTS)
        && packageName == null) {
    if (dumpState.onTitlePrinted()) {
        pw.println();
    }
    pw.println("Protected broadcast actions:");
    for (int i = 0; i < mProtectedBroadcasts.size(); i++) {
        pw.print("  ");
        pw.println(mProtectedBroadcasts.valueAt(i));
    }
}

snapshot statistics 和 protected broadcasts 都被限制为非 checkin、全量 dump。snapshot.getUsed() 是本次 dump 所使用 Computer 的 usage counter;它不是整个系统所有查询的计数。--brief 只改变 statistics 展开程度,不影响其他 section。

5. 典型输出 ​

5.1 包文本 ​

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

java
void dumpPackagesLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
        DumpState dumpState, boolean checkin) {
    final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    final Date date = new Date();
    boolean printedSomething = false;
    final boolean dumpAllComponents =
            dumpState.isOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS);
    List<UserInfo> users = getAllUsers(UserManagerService.getInstance());
    for (final PackageSetting ps : mPackages.values()) {
        if (packageName != null && !packageName.equals(ps.getRealName())
                && !packageName.equals(ps.getPackageName())) {
            continue;
        }
        if (ps.getPkg() != null && ps.getPkg().isApex()
                && !dumpState.isOptionEnabled(DumpState.OPTION_INCLUDE_APEX)) {
            continue;
        }
        final LegacyPermissionState permissionsState =
                mPermissionDataProvider.getLegacyPermissionState(ps.getAppId());
        if (permissionNames != null
                && !permissionsState.hasPermissionState(permissionNames)) {
            continue;
        }
        dumpPackageLPr(pw, "  ", checkin ? "pkg" : null, permissionNames, ps,
                permissionsState, sdf, date, users, packageName != null, dumpAllComponents);
    }
}

Packages: 下面每个条目的 owner 是 PackageSetting,不是 ComputerEngine 重新生成的 PackageInfo。过滤顺序是:目标包名(同时接受 real name)、APEX option、权限名称过滤,然后把 PackageSetting、legacy permission state、所有 user 和组件选项交给 dumpPackageLPr()。

因此看到一个包的 versionCode、path、userId、enabled/disabled、stopped、suspended 或 runtime permissions 时,应回到 PackageSetting 和 per-user state,而不是只看 APK manifest。--all-components 只影响组件名展开;--include-apex 让 APEX 不在普通 packages section 被跳过。

5.2 重命名与隐藏包 ​

同一个 Settings dump 还会追加两个独立分组:

java
if (mRenamedPackages.size() > 0 && permissionNames == null) {
    for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
        if (packageName != null && !packageName.equals(e.getKey())
                && !packageName.equals(e.getValue())) {
            continue;
        }
        if (!checkin) {
            pw.println("Renamed packages:");
        } else {
            pw.print("ren,");
        }
        pw.print(e.getKey());
        pw.print(checkin ? " -> " : ",");
        pw.println(e.getValue());
    }
}

renamed package 输出只在没有 permissionNames 过滤时出现。随后 mDisabledSysPackages 会输出 Hidden system packages:,表示系统原包被 data 更新包遮蔽后的 disabled state。单包 dump 可能同时在 active package、renamed package 和 hidden system package 分组中命中,不能看到一个包名就假设只有一条 Settings 记录。

5.3 SharedUser字段 ​

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

java
void dumpSharedUsersLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
        DumpState dumpState, boolean checkin) {
    boolean printedSomething = false;
    for (SharedUserSetting su : mSharedUsers.values()) {
        if (packageName != null && su != dumpState.getSharedUser()) {
            continue;
        }
        final LegacyPermissionState permissionsState =
                mPermissionDataProvider.getLegacyPermissionState(su.mAppId);
        if (permissionNames != null
                && !permissionsState.hasPermissionState(permissionNames)) {
            continue;
        }
        if (!checkin) {
            pw.print("  SharedUser [");
            pw.print(su.name);
            pw.print("] (");
            pw.print(Integer.toHexString(System.identityHashCode(su)));
            pw.println("):");
            pw.print("    appId=");
            pw.println(su.mAppId);
            pw.println("    Packages");
            final ArraySet<PackageStateInternal> states =
                    (ArraySet<PackageStateInternal>) su.getPackageStates();
            for (PackageStateInternal ps : states) {
                pw.println("      " + ps);
            }
        }
    }
}

SharedUser [name]、appId 和 Packages 直接对应 SharedUserSetting 的 group 状态。后面的 user permission/GID 输出仍以 shared user 的 appId 计算 UID。PMS006 已解释成员聚合;在 dump 中应重点看 group 成员是否完整、appId 是否符合预期、每个 user 是否存在 runtime permission/GID 条目。

5.4 Queries图 ​

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

java
case DumpState.DUMP_QUERIES:
{
    final Integer filteringAppId = setting == null ? null : setting.getAppId();
    mAppsFilter.dumpQueries(
            pw, filteringAppId, dumpState, mUserManager.getUserIds(),
            this::getPackagesForUidInternalBody);
    break;
}

dumpsys package queries 的 owner 是 AppsFilterSnapshot。如果指定单包,Computer 先从目标 PackageStateInternal 得到 appId,再把它作为 filtering app id;全量 dump 时 appId 为 null。输出中的 queryable/filtered 关系不是 Manifest <queries> 原文,而是 AppsFilter 根据 declarations、签名、system policy、shared UID 和 user 状态计算后的可见性图。

5.5 Frozen状态 ​

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

java
case DumpState.DUMP_FROZEN: {
    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
    ipw.println("Frozen packages:");
    ipw.increaseIndent();
    if (mFrozenPackages.size() == 0) {
        ipw.println("(none)");
    } else {
        for (int i = 0; i < mFrozenPackages.size(); i++) {
            ipw.print("package=");
            ipw.print(mFrozenPackages.keyAt(i));
            ipw.print(", refCounts=");
            ipw.println(mFrozenPackages.valueAt(i));
        }
    }
    ipw.decreaseIndent();
    break;
}

Frozen packages: 展示的是 mFrozenPackages 的 package→reference count map。refCount 大于 1 表示多个 PackageFreezer 持有同一包的冻结引用;(none) 只表示 dump snapshot 中没有冻结条目,不代表最近没有发生安装/卸载。

5.6 Dexopt状态 ​

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

java
case DumpState.DUMP_DEXOPT:
{
    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
    ipw.println("Dexopt state:");
    ipw.increaseIndent();
    DexOptHelper.dumpDexoptState(ipw, this, packageName);
    ipw.decreaseIndent();
    break;
}

Computer 侧只负责把 dump 请求转交给 ART helper;下面的实现负责创建 Local filtered view 并选择单包或全量输出。

java
public static void dumpDexoptState(@NonNull IndentingPrintWriter ipw,
        @NonNull PackageDataSnapshot computer, @Nullable String packageName) {
    try (PackageManagerLocal.FilteredSnapshot snapshot =
            getPackageManagerLocal().withUnownedFilteredSnapshot(computer)) {
        if (packageName != null) {
            DexOptHelper.getArtManagerLocal().dumpPackage(ipw, snapshot, packageName);
        } else {
            DexOptHelper.getArtManagerLocal().dump(ipw, snapshot);
        }
    }
}

Computer dump 把自身作为 PackageDataSnapshot 传入 ART 的 Local snapshot。这个 section 的 package identity 来自 PMS snapshot,dex artifacts/profiles 的实际内容由 ART owner 输出。package 在两次读取之间消失时,非 alternative flag 路径会捕获 IllegalArgumentException 并打印异常;因此 dexopt dump 为空或报 package not found 时,要同时检查 PMS snapshot 和 ART 状态。

5.7 APEX分组 ​

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

java
case DumpState.DUMP_APEX: {
    if (packageName == null || isApexPackage(packageName)) {
        mApexManager.dump(pw);
        dumpApex(pw, packageName);
    }
    break;
}

APEX section 的第二段实现负责从 package state 构造 active/inactive/factory 列表:

java
private void dumpApex(PrintWriter pw, String packageName) {
    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
    List<PackageStateInternal> activePackages = new ArrayList<>();
    List<PackageStateInternal> inactivePackages = new ArrayList<>();
    List<PackageStateInternal> factoryActivePackages = new ArrayList<>();
    List<PackageStateInternal> factoryInactivePackages = new ArrayList<>();
    generateApexPackageInfo(activePackages, inactivePackages,
            factoryActivePackages, factoryInactivePackages);
    ipw.println("Active APEX packages:");
    dumpApexPackageStates(activePackages, true, packageName, ipw);
    ipw.println("Inactive APEX packages:");
    dumpApexPackageStates(inactivePackages, false, packageName, ipw);
    ipw.println("Factory APEX packages:");
    dumpApexPackageStates(factoryActivePackages, true, packageName, ipw);
    dumpApexPackageStates(factoryInactivePackages, false, packageName, ipw);
}

APEX section先由 ApexManager.dump() 打印 manager 自身状态,再由 Computer 根据 package state 划分 active、inactive、factory active、factory inactive。IsActive 和 IsFactory 是两个不同维度:更新系统 APEX 可能 active 但不是 factory,disabled system package 可能出现在 inactive/factory inactive。不要只看路径前缀判断当前激活状态。

6. checkin与proto ​

6.1 checkin 文本 ​

--checkin 不是普通文本输出加一个 header,而是让多个 owner 走机器可读分支:

java
if (checkin) {
    pw.println("vers,1");
}

Settings 的 package dump 会使用 pkg,、dis,、ren,、suid, 等前缀;features 使用 feat,;verifier 使用 vrfy,;domain verifier 使用 dv,。resolver、preferred XML、snapshot statistics 等被 !checkin 守卫的 section 不会在 checkin 输出中出现。

6.2 Proto输出 ​

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

java
} else if ("--proto".equals(opt)) {
    dumpProto(snapshot, fd);
    return;
}

proto 选项在参数解析阶段立即返回,不使用 DumpState 文本 section。它将数据写入 FileDescriptor,因此调用 dumpsys package --proto 时 stdout 上看不到可读的 section 标题。调试 proto 时要使用对应的 PackageServiceDumpProto schema/解析工具,而不是用文本 grep。

6.3 Preferred XML ​

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

java
case DumpState.DUMP_PREFERRED_XML:
{
    pw.flush();
    TypedXmlSerializer serializer = Xml.newFastSerializer();
    try (BufferedOutputStream str =
            new BufferedOutputStream(new FileOutputStream(fd))) {
        serializer.setOutput(str, StandardCharsets.UTF_8.name());
        serializer.startDocument(null, true);
        serializer.setFeature(
                "http://xmlpull.org/v1/doc/features.html#indent-output", true);
        mSettings.writePreferredActivitiesLPr(serializer, 0,
                dumpState.isFullPreferred());
        serializer.endDocument();
        serializer.flush();
    } catch (IllegalArgumentException | IllegalStateException | IOException e) {
        pw.println("Failed writing: " + e);
    }
    break;
}

preferred XML 使用 fd 创建二进制输出流并写 XML 文档,不是把 XML 逐行打印到已有 PrintWriter。如果 serializer 或文件描述符失败,错误文本写回 pw;所以调用方可能看到部分 XML 已经写入后又出现 Failed writing。

7. 失败与诊断 ​

7.1 权限失败 ​

PMS.dump() 在任何 snapshot 或 helper 创建前调用 DumpUtils.checkDumpAndUsageStatsPermission()。权限不足时方法直接 return,输出内容由 DumpUtils 决定;不能用“没有 Packages: 标题”推断 Settings 为空。

7.2 未知 section ​

未知 option 会打印 Unknown argument: ...; use -h for help;未知 resolver 子类型和 check-permission 缺少参数则打印 Error 后 return。未知普通 section 在当前参数解析代码中不会额外打印错误,随后保持 mTypes == 0,可能触发默认全量 dump。排查脚本必须先确认命令是否真的设置了 section bit。

7.3 单包不存在 ​

普通未知包在 DumpHelper guard 中打印 Unable to find package: <name> 并返回;APEX package 例外允许进入 APEX section。包已重命名时,guard 使用 snapshot.getPackageStateInternal() 的当前名称解析能力,Settings package dump 又同时接受 real name,因此要结合 renamed packages section 判断名称是否是历史别名。

7.4 状态竞争 ​

PMS dump 使用 snapshot,但 Settings、permission、Installer、DomainVerification 和 APEX manager 仍可能在各自 owner 内读取其他状态。dump 是诊断时刻的视图,不是跨所有 subsystem 的事务快照。安装/卸载正在进行时,可能看到:

  • package state 已更新,但 post-install/广播尚未完成;
  • Frozen packages 中仍有 refCount;
  • package list 与 APEX manager 状态处于不同阶段;
  • dexopt/ART profile 正在异步清理;
  • Settings 已 schedule cache invalidation,但文件尚未落盘。

这些组合并不自动说明 dump 错误,需要沿 PMS013 的消息和 PMS012 的锁/I/O 边界继续判断。

7.5 常用诊断组合 ​

目标命令源码 owner
完整文本状态dumpsys packageDumpHelper 默认 section
单包与 filterdumpsys package com.example.appSettings + resolver,自动 show filters
权限判定dumpsys package check-permission PERM com.example.app 0PermissionManager
resolverdumpsys package resolvers activityComponentResolverSnapshot
package visibilitydumpsys package queries com.example.appAppsFilterSnapshot
shared UIDdumpsys package shared-usersSettings/SharedUserSetting
冻结状态dumpsys package frozenComputerEngine.mFrozenPackages
dexoptdumpsys package dexopt com.example.appART Local + Computer
APEXdumpsys package apexApexManager + Computer
snapshot 统计dumpsys package snapshot --fullSnapshotStatistics
机器可读dumpsys package --checkincheckin 分支
protodumpsys package --protoproto dump

命令只是入口提示,最终判断仍要回到 section 的 owner 和失败语义。例如 queries 输出少不一定是 <queries> 没声明,也可能是 AppsFilter 因签名或 user policy 过滤;dexopt 没有 package 条目也可能是 package race,而不是 ART service 未运行。

8. 测试与证明范围 ​

8.1 DumpState测试 ​

测试输入可以是空 DumpState、逐个 setDump() 的 type 和 option:

  1. 空状态下断言普通 type 的 isDumping() 为 true;
  2. 空状态下断言 DUMP_PREFERRED_XML 为 false;
  3. 设置一个 type 后断言该 type 为 true、未设置 type 为 false;
  4. 多次 setDump() 后断言 bitwise OR 行为;
  5. onTitlePrinted() 第一次返回 false,后续返回 true;
  6. setCheckIn()、setBrief()、target package 和 shared user getter/setter 保持值。

这些测试只能证明 DumpState 容器,不证明 DumpHelper 的参数消费顺序和输出 owner。

8.2 参数测试 ​

应使用可控 Computer、PermissionManager、Settings 和 PrintWriter,覆盖:

  • -h/--proto 立即返回,不执行文本 section;
  • check-permission 缺 permission/package/user 格式错误时只输出 Error;
  • resolvers 无子类型打开四个位,有子类型只打开指定 resolver;
  • 单包自动开启 OPTION_SHOW_FILTERS,未知普通包输出 Unable to find;
  • snapshot --brief/--full 设置 brief 状态;
  • --checkin 跳过被 !checkin 守卫的 resolver、snapshot statistics 和 protected broadcasts。

断言应检查调用了哪个 owner、传入的 packageName/checkin/option 是否正确,而不是只检查输出包含某个标题。

8.3 输出字段测试 ​

Settings dump 测试应准备:active package、renamed package、disabled system package、shared UID 和 per-user state,分别断言它们落入正确分组。AppsFilter dump 测试应准备 calling/target package state 和 user ids,断言 filtering app id 以及 query edges。APEX 测试应准备 active updated APEX 和 factory APEX,断言 active/inactive/factory 分组。

8.4 手工命令边界 ​

单次 dumpsys package 不能证明:

  • 所有 package state 都已持久化到 packages.xml;
  • dump 期间没有安装/卸载或 dexopt race;
  • PackageManagerInternal 的 calling UID 过滤和 dump 输出完全相同;
  • proto、checkin 与普通文本包含完全相同的数据;
  • APEX stage observer、Handler 消息和 post-install 回调已经完成。

要证明这些内容,需要结合 Settings 文件、Handler/trace、Installer/ART 日志、staged install 测试和 snapshot 统计,而不是只复制一段 dump 文本。

9. 阅读路线 ​

读一段新的 dumpsys package 输出时,可以按以下顺序回到源码:

  1. 先确认命令参数是否被 DumpHelper 识别,记录 DumpState.mTypes、options、checkin、brief 和 target package。
  2. 找 DumpHelper.doDump() 中对应的 section 顺序,确认它是否被 !checkin、packageName == null 或 permissionNames 守卫。
  3. 确定输出 owner:Computer、Settings、resolver、PermissionManager、Installer、DomainVerification、StorageEvent、ApexManager 或 SnapshotStatistics。
  4. 对 package 条目回到 PackageSetting 和 per-user state;对 queries 回到 AppsFilter;对 resolver 回到 ComponentResolver snapshot;对 dexopt 回到 PackageManagerLocal/ART。
  5. 对空结果先检查 guard、user、APEX option、visibility、archive/disabled state 和 payload,而不是立即判定“系统没有这个包”。
  6. 对延迟/异步状态,结合 PackageHandler 消息、PackageFreezer、Installer I/O 和 snapshot 时点判断是否只是中间状态。
  7. 需要机器处理时先选择 --checkin 或 --proto,不要把普通文本标题当稳定 schema。
  8. 最后用单元/集成测试的输入、动作、断言和未覆盖范围校验结论。

小结 ​

Android 17 的 dumpsys package 是一条由多个 owner 协作完成的诊断管线:

  • PMS Binder dump() 先检查权限、取得 Computer snapshot,并复制 known packages/features/protected broadcasts;
  • DumpHelper 把参数转成 DumpState 位图和过滤上下文,section 按源码固定顺序执行;
  • 空 mTypes 默认输出大多数 section,但 preferred XML 必须显式请求;--proto 则在解析阶段直接切换到 proto 输出;
  • packages/shared users 由 Settings/PackageSetting 负责,resolver 由 ComponentResolver,queries 由 AppsFilter,权限由 PermissionManager,dexopt 由 ART Local,APEX 由 ApexManager 与 Computer 协作;
  • 单包 dump 自动显示 filter,未知普通包提前返回,APEX package 有单独例外;
  • checkin、普通文本和 proto 是不同输出协议,不能互相当作稳定格式替换;
  • dump 使用 snapshot 但不是跨 PMS、PermissionManager、Installer、ART、DomainVerification 的全局事务,安装、清理、验证和持久化中间状态可能同时出现;
  • 真正的教材级诊断必须把命令参数、位图、section 守卫、owner、状态字段、异步时点和测试证明范围连起来。

基础与启动部分到此完成。后续包扫描篇将从 InitAppsHelper 和 scanDirTracedLI() 继续,把这里 dump 中看到的 package state、APEX 分组和 resolver 条目追溯到系统启动扫描的源头。