cmd package 命令
本文面向需要通过 adb shell cmd package 定位 PMS 行为的读者。它不提供把几十个子命令排成速查表,而是解释命令从 Binder 入口如何进入 PackageManagerShellCommand,参数和 userId 如何生效,为什么有些命令直接调用 PMS,有些命令转给 PackageInstaller、ART Service 或 UserManagerService。
1. Binder 入口
源码文件: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);
}cmd package 不是直接执行某个静态命令函数,而是通过 package Binder service 创建 PackageManagerShellCommand,由 ShellCommand.exec() 负责参数流和 stdout/stderr。命令的调用 UID 仍可在下层 Binder/PMS API 中被检查。
2. onCommand 分派
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:onCommand
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
try {
switch (cmd) {
case "path":
return runPath();
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 "create-user":
return runCreateUser();
case "remove-user":
return runRemoveUser();
case "compile":
return runCompile();
default:
return handleDefaultCommands(cmd);
}
} catch (Exception e) {
getErrPrintWriter().println(
"Error: " + e.getMessage());
return -1;
}
}Android 17 的真实 switch 还包含 query、permission flags、user restrictions、domain verification、archive、ART/APEX 等命令。关键结构是每个命令都有一个 runXxx() 入口,命令之间共享参数解析工具,但不共享同一套业务锁或状态 owner。
3. 用户参数
大量命令支持 --user,但 userId 解析不是简单 Integer.parseInt。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:translateUserId
private int translateUserId(int userId,
int allUserId, String logContext) {
final boolean allowAll =
allUserId != UserHandle.USER_NULL;
final int translatedUserId =
ActivityManager.handleIncomingUser(
Binder.getCallingPid(),
Binder.getCallingUid(), userId,
allowAll, true, logContext,
"pm command");
return translatedUserId == UserHandle.USER_ALL
? allUserId : translatedUserId;
}USER_CURRENT、USER_ALL 和跨用户权限由 ActivityManager 的 incoming-user 检查统一处理。一个命令显示 --user all 并不代表 shell 可以任意修改所有用户;allowAll、调用 UID 和命令业务权限共同决定结果。
4. 查询命令
4.1 path
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:runPath
private int runPath() throws RemoteException {
String packageName = getNextArg();
if (packageName == null) {
getErrPrintWriter().println(
"Error: package name not specified");
return 1;
}
String[] paths = mInterface.getApplicationCodePaths(
packageName);
PrintWriter pw = getOutPrintWriter();
for (String path : paths) {
pw.println("package:" + path);
}
return 0;
}path 返回 code paths,不等于用户数据目录,也不等于 APEX active mount 的全部依赖。APEX、split APK、APK-in-APEX 的路径含义不同,排查时要配合 dumpsys package 和 ApexManager 信息。
4.2 list
list packages、list permission-groups、list features 等命令共享 runList 的选项解析。-u、--user、-a 等选项会改变 PMS 查询 flags 和 user scope;不能只看命令主词判断输出集合。
5. 安装与会话
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:runInstall 与 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);
}shell 的 install 命令最终进入 PackageInstallerService session,而不是直接调用 InstallPackageHelper。--user all 会设置 INSTALL_ALL_USERS,但目标用户仍经 translateUserId 处理;staged APEX、APK session、multi-package session 的后续流程由 PackageInstaller/StagingManager 决定。
6. install-existing
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:runInstallExisting
install-existing PACKAGE --user USER_ID 走 IPackageManager.installExistingPackageAsUser,只为目标用户修改 PackageUserState.installed 并准备 app data。它不会复制 APK,也不会重新执行完整 APK 解析;这正是排查多用户“包已存在但当前用户看不到”最直接的命令入口。
相关源码:
frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java:权限、状态写入、数据准备和PACKAGE_ADDEDframeworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java:命令参数解析
7. 状态命令
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:runSetEnabledSetting
private int runSetEnabledSetting(int state)
throws RemoteException {
String packageName = getNextArg();
int userId = UserHandle.USER_SYSTEM;
String opt;
while ((opt = getNextOption()) != null) {
if (opt.equals("--user")) {
userId = UserHandle.parseUserArg(
getNextArgRequired());
}
}
mInterface.setApplicationEnabledSetting(
packageName, state, 0,
userId, "shell");
return 0;
}enable/disable/disable-user/default-state 最终修改 per-user enabled state;hide/unhide 修改 hidden;suspend/unsuspend 修改 suspend params;clear 进入用户数据清理。命令相似,但状态 owner 和副作用不同。
8. 权限命令
源码文件: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());
}
}
if (grant) {
mInterface.grantRuntimePermission(
packageName, permissionName,
userId);
} else {
mInterface.revokeRuntimePermission(
packageName, permissionName,
userId, "shell");
}
return 0;
}grant/revoke 修改的是 PermissionManager 的 per-user runtime permission,不是 PackageUserState.enabled。命令成功只能说明 Binder API 接受了请求,权限保护级别、目标包声明和 user scope 仍由 PermissionManager 校验。
9. 用户命令
create-user、remove-user、mark-guest-for-deletion、set-user-restriction 等命令通过 PMS 暴露的接口或 UserManager 内部服务进入 UMS。它们不是“包命令”意义上的直接 PackageSetting 修改;用户创建/删除会触发 UMS → PMS 的完整生命周期回调。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:用户命令分派
case "create-user":
return runCreateUser();
case "remove-user":
return runRemoveUser();
case "mark-guest-for-deletion":
return runMarkGuestForDeletion();
case "set-user-restriction":
return runSetUserRestriction();调试用户问题时,命令结果应继续追到 UserManagerService、Settings、UserDataPreparer 和 PMS cleanUpUser,而不是停留在 shell 输出。
10. ART/APEX 命令
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java,符号:ART service compatibility set
private static final Set<String> ART_SERVICE_COMMANDS = Set.of(
"compile", "reconcile-secondary-dex-files",
"force-dex-opt", "bg-dexopt-job",
"cancel-bg-dexopt-job", "delete-dexopt",
"dump-profiles", "snapshot-profile", "art");旧 pm compile 等命令保留在兼容集合中,但新 ART 命令通过 art namespace 转发到 ArtManagerLocal.handleShellCommand。同理,APEX staged 查询/管理命令通常还要进入 PackageInstaller/StagingManager/ApexManager,不能从 shell 命令名推断真正 owner。
11. 调试路径
| 现象 | 入口 | 继续追踪 |
|---|---|---|
| 包路径异常 | cmd package path | PackageSetting、APEX active path |
| 用户看不到包 | install-existing --user / list packages --user | PackageUserState、AppsFilter |
| 权限异常 | grant/revoke、permission flags | PermissionManager |
| 组件状态异常 | enable/disable/hide/suspend | per-user state、resolver |
| staged 安装异常 | get-stagedsessions、dump | StagingManager、ApexManager、apexd |
| dexopt 异常 | compile、art、bg-dexopt-job | DexOptHelper、ArtManagerLocal |
12. 错误语义
shell 命令的返回码只表达命令层是否完成;业务失败可能以 RemoteException、SecurityException、PackageManager install code、打印到 stderr 的文本或异步 session status 返回。调试安装、staged APEX 和 rollback 时必须同时读取 session/服务状态,不能只看命令退出码。
13. 阅读检查
沿源码复述:cmd package → package Binder onShellCommand → PackageManagerShellCommand.onCommand → 对应 runXxx → PMS/PackageInstaller/UserManager/PermissionManager/ART/ApexManager。然后回答:install-existing 是否复制 APK?grant 是否修改 PackageUserState?cmd package compile 是否直接在 ShellCommand 中运行 dex2oat?答案分别是“不复制”“不修改,它进入 PermissionManager”“不直接运行,转入 ART Service”。
