Skip to content

cmd package 命令

追踪 PackageManagerShellCommand 的入口、分派、权限、用户参数和典型调试路径。

基于android-17.0.0_r1
AndroidPMSDebuggingShellCommand

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

java
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

java
@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

java
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

java
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

java
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_ADDED
  • frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java:命令参数解析

7. 状态命令 ​

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

java
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

java
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,符号:用户命令分派

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

java
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 pathPackageSetting、APEX active path
用户看不到包install-existing --user / list packages --userPackageUserState、AppsFilter
权限异常grant/revoke、permission flagsPermissionManager
组件状态异常enable/disable/hide/suspendper-user state、resolver
staged 安装异常get-stagedsessions、dumpStagingManager、ApexManager、apexd
dexopt 异常compile、art、bg-dexopt-jobDexOptHelper、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”。