Skip to content

pm 命令集合

对照 Android 17 源码理解 pm 的命令透传、查询过滤、安装会话、权限和用户参数。

基于android-17.0.0_r1
AndroidPMSDebuggingPackageInstaller

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

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);
}

pm 与 cmd package 最终都进入这个 Binder shell command 对象;差异主要来自启动脚本和调用方式,不是两套 PMS 实现。

2. 命令分派 ​

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

java
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

java
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

java
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,符号:列表过滤与输出

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

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);
}

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

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());
        }
    }
    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,符号:相关分派

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/pathuser scope、包可见性、APEX flagsstdout + PMS query
installsession validation、verification、data prepstderr + session status
install-existingpermission、user restriction、包不存在return code + log
grant/revokepermission protection、声明缺失、跨用户Binder exception
enable/hide/suspendcomponent/state policyPackageUserState + restrictions
create/remove userUMS type/limit/storage/stop callbackUserManager + PMS callbacks
compile/artART Service reason/filter/workerART logs + DexoptResult

10. 实际排查路径 ​

text
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”。