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
@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);
}入口的顺序有实际含义:
DumpUtils.checkDumpAndUsageStatsPermission()失败时直接返回,后续不会泄露任何 dump 内容;snapshotComputer()为包、组件和可见性相关 section 提供统一查询视图;KnownPackages、features、protected broadcasts 被复制或在局部锁内复制,避免DumpHelper遍历 PMS live 集合;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
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 snapshot | version、libraries、preferred、queries、dexopt、frozen、APEX、消息等查询型 section |
Settings(通过 Computer 或内部状态) | packages、shared users、preferred XML、读取消息 |
PermissionManagerServiceInternal | permissions、check-permission |
ComponentResolverApi | Activity/Service/Receiver/Provider resolver tables |
PackageInstallerService | installs/session 状态 |
DomainVerificationManagerInternal | domain verifier/preferred 状态 |
StorageEventHelper | volumes |
KnownPackages/PMS copies | known packages、features、protected broadcasts |
SnapshotStatistics | snapshot statistics |
当某个 section 输出异常时,应先定位 owner,再判断 snapshot、锁或持久化问题;不要只在 PackageManagerService.dump() 入口处加日志。
2. DumpState
2.1 section 位图
源码文件:frameworks/base/services/core/java/com/android/server/pm/DumpState.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
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 还保存两个过滤上下文:
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
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
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
} 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
} 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
} 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
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
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
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
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
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
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
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 还会追加两个独立分组:
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
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
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
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
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 并选择单包或全量输出。
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
case DumpState.DUMP_APEX: {
if (packageName == null || isApexPackage(packageName)) {
mApexManager.dump(pw);
dumpApex(pw, packageName);
}
break;
}APEX section 的第二段实现负责从 package state 构造 active/inactive/factory 列表:
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 走机器可读分支:
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
} 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
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 package | DumpHelper 默认 section |
| 单包与 filter | dumpsys package com.example.app | Settings + resolver,自动 show filters |
| 权限判定 | dumpsys package check-permission PERM com.example.app 0 | PermissionManager |
| resolver | dumpsys package resolvers activity | ComponentResolverSnapshot |
| package visibility | dumpsys package queries com.example.app | AppsFilterSnapshot |
| shared UID | dumpsys package shared-users | Settings/SharedUserSetting |
| 冻结状态 | dumpsys package frozen | ComputerEngine.mFrozenPackages |
| dexopt | dumpsys package dexopt com.example.app | ART Local + Computer |
| APEX | dumpsys package apex | ApexManager + Computer |
| snapshot 统计 | dumpsys package snapshot --full | SnapshotStatistics |
| 机器可读 | dumpsys package --checkin | checkin 分支 |
| proto | dumpsys package --proto | proto dump |
命令只是入口提示,最终判断仍要回到 section 的 owner 和失败语义。例如 queries 输出少不一定是 <queries> 没声明,也可能是 AppsFilter 因签名或 user policy 过滤;dexopt 没有 package 条目也可能是 package race,而不是 ART service 未运行。
8. 测试与证明范围
8.1 DumpState测试
测试输入可以是空 DumpState、逐个 setDump() 的 type 和 option:
- 空状态下断言普通 type 的
isDumping()为 true; - 空状态下断言
DUMP_PREFERRED_XML为 false; - 设置一个 type 后断言该 type 为 true、未设置 type 为 false;
- 多次
setDump()后断言 bitwise OR 行为; onTitlePrinted()第一次返回 false,后续返回 true;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 输出时,可以按以下顺序回到源码:
- 先确认命令参数是否被
DumpHelper识别,记录DumpState.mTypes、options、checkin、brief 和 target package。 - 找
DumpHelper.doDump()中对应的 section 顺序,确认它是否被!checkin、packageName == null或permissionNames守卫。 - 确定输出 owner:Computer、Settings、resolver、PermissionManager、Installer、DomainVerification、StorageEvent、ApexManager 或 SnapshotStatistics。
- 对 package 条目回到
PackageSetting和 per-user state;对 queries 回到 AppsFilter;对 resolver 回到 ComponentResolver snapshot;对 dexopt 回到 PackageManagerLocal/ART。 - 对空结果先检查 guard、user、APEX option、visibility、archive/disabled state 和 payload,而不是立即判定“系统没有这个包”。
- 对延迟/异步状态,结合 PackageHandler 消息、PackageFreezer、Installer I/O 和 snapshot 时点判断是否只是中间状态。
- 需要机器处理时先选择
--checkin或--proto,不要把普通文本标题当稳定 schema。 - 最后用单元/集成测试的输入、动作、断言和未覆盖范围校验结论。
小结
Android 17 的 dumpsys package 是一条由多个 owner 协作完成的诊断管线:
- PMS Binder
dump()先检查权限、取得Computersnapshot,并复制 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 条目追溯到系统启动扫描的源头。
