pm 命令集合
本文承接 cmd package 命令。PMS141 讲了 shell service 的总体分派;本文只解决 pm 实际使用中的四个问题:pm 与 cmd package 是否共用入口,list 的过滤到底在哪执行,安装/权限/用户命令分别进入哪个服务,以及命令输出为何不能直接当作最终状态。
1. pm 的真实边界
在 Android 17 中,pm 是面向 shell 的命令入口,业务实现位于 PackageManagerShellCommand。它可以调用 PMS Binder,也可以转给 PackageInstaller、UserManager、PermissionManager、ART Service 或 Domain Verification shell。命令名相似不代表状态 owner 相同。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:onShellCommand
public void onShellCommand(FileDescriptor in,
FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
new PackageManagerShellCommand(
this, mContext,
mDomainVerificationManager.getShell())
.exec(this, in, out, err, args,
callback, resultReceiver);
}pm 与 cmd package 最终都进入这个 Binder shell command 对象;差异主要来自启动脚本和调用方式,不是两套 PMS 实现。
2. 命令分派
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:onCommand
switch (cmd) {
case "list":
return runList();
case "install":
return runInstall();
case "install-existing":
return runInstallExisting();
case "uninstall":
return runUninstall();
case "grant":
return runGrantRevokePermission(true);
case "revoke":
return runGrantRevokePermission(false);
case "enable":
return runSetEnabledSetting(
PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
case "create-user":
return runCreateUser();
case "compile":
return runCompile();
default:
return handleDefaultCommands(cmd);
}调试时先从 onCommand 找到 runXxx,再追它调用的 Binder 接口;不要根据 pm 的命令名直接猜测调用了哪个内部类。
3. 用户解析
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:translateUserId
private int translateUserId(int userId,
int allUserId, String logContext) {
boolean allowAll =
allUserId != UserHandle.USER_NULL;
int translated = ActivityManager.handleIncomingUser(
Binder.getCallingPid(),
Binder.getCallingUid(), userId,
allowAll, true, logContext,
"pm command");
return translated == UserHandle.USER_ALL
? allUserId : translated;
}--user current/all/<id> 先经过 ActivityManager 的 incoming-user 检查。pm list packages --user all 可以展开多个用户,但 pm install-existing --user 10 是否允许仍取决于具体 Binder 权限和 user restriction。
4. list packages
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:runListPackages
int getFlags = 0;
boolean listDisabled = false;
boolean listEnabled = false;
boolean listSystem = false;
boolean listThirdParty = false;
boolean listApexOnly = false;
boolean showUid = false;
int uid = -1;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-d": listDisabled = true; break;
case "-e": listEnabled = true; break;
case "-s": listSystem = true; break;
case "-3": listThirdParty = true; break;
case "-U": showUid = true; break;
case "-u":
getFlags |= PackageManager.MATCH_UNINSTALLED_PACKAGES;
break;
case "--apex-only":
getFlags |= PackageManager.MATCH_APEX;
listApexOnly = true;
break;
case "--factory-only":
getFlags |= PackageManager.MATCH_FACTORY_ONLY;
break;
case "--user":
defaultUserId = UserHandle.parseUserArg(
getNextArgRequired());
break;
}
}选项先被转换为 PackageManager flags 和本地输出过滤布尔值。-u 影响 PMS 返回集合,-d/-e/-s/-3 主要在 shell 层继续筛选;--apex-only 同时设置 MATCH_APEX 和本地 isApex 判断。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:列表过滤与输出
ParceledListSlice<PackageInfo> slice =
mInterface.getInstalledPackages(
getFlags, translatedUserId);
for (PackageInfo info : slice.getList()) {
boolean isApex = info.isApex;
boolean isSystem = !isApex
&& (info.applicationInfo.flags
& ApplicationInfo.FLAG_SYSTEM) != 0;
boolean isEnabled = !isApex
&& info.applicationInfo.enabled;
if ((listDisabled && isEnabled)
|| (listEnabled && !isEnabled)
|| (listSystem && !isSystem)
|| (listThirdParty && isSystem)
|| (listApexOnly && !isApex)) {
continue;
}
if (uid != -1 && !isApex
&& info.applicationInfo.uid != uid) {
continue;
}
}输出中的 enabled、uid、isApex 都来自指定 user 的 PackageInfo。其中 APEX 没有普通应用 UID 过滤语义,因此 --uid 分支显式跳过 APEX。
5. 安装会话
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:doCreateSession
private int doCreateSession(
SessionParams params,
String installerPackageName, int userId)
throws RemoteException {
if (userId == UserHandle.USER_ALL) {
params.installFlags |=
PackageManager.INSTALL_ALL_USERS;
}
int translatedUserId = translateUserId(
userId, UserHandle.USER_SYSTEM,
"doCreateSession");
return mInterface.getPackageInstaller()
.createSession(params,
installerPackageName, null,
translatedUserId);
}pm install 不是在 shell 类中复制文件并直接改 PMS;它创建 PackageInstaller session,再由 session 处理 APK/APEX、验证、用户范围和提交。--user all 先留下 INSTALL_ALL_USERS 标志,再把传入 user 翻译为服务可接受的值。
6. install-existing
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:runInstallExisting
pm install-existing PACKAGE --user USER_ID 调用 IPackageManager.installExistingPackageAsUser。它不会重新复制或扫描 APK,核心副作用是目标 user 的 PackageUserState.installed=true、数据准备、权限初始化、PACKAGE_ADDED 广播和可选恢复回调。
这条命令适合验证“全局包存在但某用户未安装”的问题;如果目标包没有全局 PackageSetting,命令失败不能说明用户目录损坏。
7. 权限命令
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:runGrantRevokePermission
private int runGrantRevokePermission(
boolean grant) throws RemoteException {
String packageName = getNextArgRequired();
String permissionName = getNextArgRequired();
int userId = UserHandle.USER_SYSTEM;
String opt;
while ((opt = getNextOption()) != null) {
if (opt.equals("--user")) {
userId = UserHandle.parseUserArg(
getNextArgRequired());
}
}
userId = translateUserId(userId,
UserHandle.USER_NULL,
"runGrantRevokePermission");
if (grant) {
mInterface.grantRuntimePermission(
packageName, permissionName, userId);
} else {
mInterface.revokeRuntimePermission(
packageName, permissionName, userId,
"shell");
}
return 0;
}grant/revoke 修改 PermissionManager 的 per-user runtime permission;它们不改变 PackageUserState 的 installed/enabled 字段。命令成功后,应用进程是否立刻感知还取决于 PermissionManager 的缓存和进程生命周期。
8. 状态与用户命令
enable、disable-user、hide、unhide、suspend、clear 分别修改不同状态或数据;create-user、remove-user、mark-guest-for-deletion 则进入 UserManagerService,并触发 PMS 生命周期回调。命令输出是入口结果,不是所有异步后处理已经完成的证明。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:相关分派
case "enable":
return runSetEnabledSetting(
PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
case "suspend":
return runSuspend(true, 0);
case "clear":
return runClear();
case "create-user":
return runCreateUser();
case "remove-user":
return runRemoveUser();排查时应从命令继续到 PackageSetting、PermissionManager、AppDataHelper 或 UserManagerService,而不是把所有命令都归为“PMS 改 XML”。
9. 输出与失败
| 命令族 | 常见失败来源 | 观察位置 |
|---|---|---|
| list/path | user scope、包可见性、APEX flags | stdout + PMS query |
| install | session validation、verification、data prep | stderr + session status |
| install-existing | permission、user restriction、包不存在 | return code + log |
| grant/revoke | permission protection、声明缺失、跨用户 | Binder exception |
| enable/hide/suspend | component/state policy | PackageUserState + restrictions |
| create/remove user | UMS type/limit/storage/stop callback | UserManager + PMS callbacks |
| compile/art | ART Service reason/filter/worker | ART logs + DexoptResult |
10. 实际排查路径
pm list packages --user 10 -u
-> 是否存在全局 PackageSetting 与 user state
pm install-existing --user 10 package
-> 是否能把 installed 写入 user 10
pm path package
-> code path 是否指向正确 APK/APEX
pm dump package
-> version、flags、installer、user state这组命令必须组合使用:list 说明查询视图,install-existing 改变用户状态,path 说明代码位置,dump 才能把全局和 per-user 状态放在一起观察。
11. 阅读检查
复述主线:pm → package Binder shell → PackageManagerShellCommand → 参数/user 翻译 → 对应 Binder owner。然后回答:pm list packages -u 是否修改包状态?pm install-existing 是否重新解析 APK?pm grant 是否启用应用?答案分别是“不修改”“不重新解析,只启用已有包到用户”“不启用,它修改 runtime permission”。
