Skip to content

PackageManager 实践

从客户端代理和 PMS 查询源码提炼 PackageManager 的 user、flags、可见性、Intent 和安装实践。

基于android-17.0.0_r1
AndroidPMSPackageManagerAPI

PackageManager 实践 ​

本文面向在应用或系统组件中调用 PackageManager 的开发者。它承接 cmd package 命令 和 pm 命令集合,但不重复 shell 调试;本文关注 API 调用方如何根据源码选择 user 范围、查询 flags、错误处理和异步安装方式。

“最佳实践”在这里不是风格建议,而是由真实实现推导出的调用约束:客户端 API 会把当前 user 和 flags 传给 PMS;PMS 再做包可见性、安装状态和权限检查。调用方如果忽略这些边界,最常见结果不是崩溃,而是收到 NameNotFoundException、空列表或只对一个用户生效。

1. 客户端代理 ​

源码文件:frameworks/base/core/java/android/app/ApplicationPackageManager.java,符号:getPackageInfoAsUser、getApplicationInfoAsUser

java
public PackageInfo getPackageInfo(
        String packageName, int flags)
        throws NameNotFoundException {
    return getPackageInfoAsUser(
            packageName, flags, getUserId());
}

public PackageInfo getPackageInfoAsUser(
        String packageName, int flags, int userId)
        throws NameNotFoundException {
    return getPackageInfoAsUserCached(
            packageName, PackageInfoFlags.of(flags),
            userId);
}

不带 AsUser 的 API 使用 ApplicationPackageManager 绑定的当前 user;跨用户查询必须显式使用 AsUser 版本并满足系统权限。客户端缓存是否启用由具体 API 和 flags 决定,但缓存不会绕过 PMS 的可见性规则。

源码文件:frameworks/base/core/java/android/app/ApplicationPackageManager.java,符号:Intent 查询

java
public List<ResolveInfo> queryIntentActivitiesAsUser(
        Intent intent, ResolveInfoFlags flags,
        int userId) {
    try {
        ParceledListSlice<ResolveInfo> slice =
                mPM.queryIntentActivities(
                        intent, intent.resolveTypeIfNeeded(
                                mContext.getContentResolver()),
                        flags, userId);
        return slice != null
                ? slice.getList()
                : Collections.emptyList();
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

Intent 查询同样带 userId,返回空列表是合法结果,不等于系统没有任何匹配组件;目标包可能被用户状态、包可见性或 cross-profile 规则过滤。

2. flags 先于调用 ​

源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java,符号:MATCH flags

java
public static final int MATCH_UNINSTALLED_PACKAGES =
        0x00002000;
public static final int MATCH_APEX = 0x40000000;
public static final int MATCH_KNOWN_PACKAGES =
        MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER;

flags 决定 PMS 允许返回哪些状态:MATCH_UNINSTALLED_PACKAGES 扩大到已知但未安装/归档的包,MATCH_APEX 纳入 APEX,MATCH_KNOWN_PACKAGES 组合了跨用户已知包语义。不要为了“保险”把所有 flags 都打开;每个 flag 都可能扩大结果范围和权限要求。

源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java,符号:getPackageInfo 注释契约

java
/**
 * @throws NameNotFoundException if no such package is available to the
 *         caller, or if the package is not installed for the calling user
 *         when MATCH_UNINSTALLED_PACKAGES is not set.
 */
public abstract PackageInfo getPackageInfo(
        @NonNull String packageName, int flags)
        throws NameNotFoundException;

NameNotFoundException 表示“对调用方当前查询条件不可用”,不一定表示设备上没有这个包。诊断时先检查 userId、flags 和可见性,再判断是否真的不存在。

3. 包可见性 ​

应用查询包列表时,PMS 会结合调用方 UID、<queries>、QUERY_ALL_PACKAGES、目标包属性和 Intent/provider 关系进行过滤。调用方的正确做法是声明最小查询需求,并让 API 失败/空结果成为可处理分支,而不是依赖“设备上所有包都可见”的假设。

源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java,符号:MATCH_UNINSTALLED_PACKAGES 权限说明

java
/**
 * Flag for getInstalledPackages(), getInstalledApplications(), ...
 * to return information about packages that are installed for any user,
 * or known but not installed for the calling user.
 * Note: use of this flag requires QUERY_ALL_PACKAGES.
 */
public static final int MATCH_UNINSTALLED_PACKAGES =
        0x00002000;

需要“已知但当前用户未安装”的信息时,调用方必须同时满足权限和 API 语义;普通业务不要用它来统计“用户实际安装数量”。

4. 选择单包还是批量 ​

源码文件:frameworks/base/core/java/android/app/ApplicationPackageManager.java,符号:单包与批量查询

java
public PackageInfo getPackageInfoAsUser(
        String packageName,
        PackageInfoFlags flags, int userId)
        throws NameNotFoundException {
    return getPackageInfoAsUserCached(
            packageName, flags, userId);
}

public List<PackageInfo> getInstalledPackagesAsUser(
        PackageInfoFlags flags, int userId) {
    try {
        return mPM.getInstalledPackages(
                updateFlagsForPackage(flags, userId),
                userId).getList();
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

已知目标包时优先单包查询,避免传输整个 PackageInfo 列表;需要批量处理时再用 getInstalledPackagesAsUser,并只请求必要 flags。批量返回还要考虑 ParceledListSlice 的 IPC 分片和调用方内存。

5. Intent 解析 ​

源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java,符号:query/resolve API

java
public abstract List<ResolveInfo>
        queryIntentActivities(
                @NonNull Intent intent, int flags);

public abstract ResolveInfo resolveActivity(
        @NonNull Intent intent, int flags);

需要展示候选列表时使用 query;只需要一个最终组件时使用 resolve。两者都受 user scope、组件 enabled 状态、包可见性和 cross-profile resolver 影响。启动前不要把 query 非空当作无条件成功,真正 startActivity 仍可能因权限或运行状态失败。

6. 签名查询 ​

源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java,符号:现代签名 API

java
public abstract boolean hasSigningCertificate(
        @NonNull String packageName,
        @NonNull byte[] cert, int type);

public abstract SigningInfo getPackageInfo(
        @NonNull String packageName,
        @PackageInfoFlags int flags)
        throws NameNotFoundException;

新代码应使用 hasSigningCertificate 或 SigningInfo/SigningDetails 语义,而不是依赖旧的 GET_SIGNATURES 数组比较。签名轮换、旧签名能力和调用 user 都可能影响结果;证书匹配不是包名存在性检查的替代。

7. install-existing ​

源码文件:frameworks/base/core/java/android/app/ApplicationPackageManager.java,符号:installExistingPackageAsUser

java
private int installExistingPackageAsUser(
        String packageName, int installReason,
        int userId) throws NameNotFoundException {
    try {
        int result = mPM.installExistingPackageAsUser(
                packageName, userId,
                PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
                installReason, null, null);
        if (result < 0) {
            throw new NameNotFoundException(
                    "Package " + packageName
                            + " could not be installed");
        }
        return result;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

该 API 只把已有全局包启用到指定用户,不重新复制 APK。它可能触发权限初始化、app data 准备和 PACKAGE_ADDED 广播;调用方应根据返回码和异常处理用户限制、包不存在和跨用户权限失败。

8. 清数据 ​

源码文件:frameworks/base/core/java/android/app/ApplicationPackageManager.java,符号:clearApplicationUserData

java
public boolean clearApplicationUserData(
        String packageName,
        IPackageDataObserver observer) {
    try {
        mPM.clearApplicationUserData(
                packageName, observer,
                getUserId(), false);
        return true;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

clear 只清目标 user 的应用数据,包代码和 installed 状态仍可保留;卸载则进入删除/保留数据策略。需要清理 profile、CE/DE、code cache 的调用方应选择正确的 API 和 user 范围,不要用 clear 模拟 uninstall。

9. 安装会话 ​

复杂安装应使用 PackageInstaller.Session:创建 session、写入 APK/APEX、commit 并监听 status receiver。PackageManager 客户端不会同步等待所有验证、dexopt、广播和数据准备;调用方应把安装结果回调视为状态转换,而不是单个 Binder 调用的返回值。

10. 失败定位 ​

现象首先检查
NameNotFoundExceptionuserId、flags、包可见性、installed state
query 返回空Intent filter、组件 enabled、用户/跨 profile 规则
批量查询慢是否请求了不必要的 metadata/组件/权限 flags
install-existing 失败安装权限、跨用户权限、user restriction、全局包记录
clear 后应用仍存在这是清数据,不是卸载;检查 data 与 PackageUserState
签名判断错误SigningInfo、证书轮换、调用 user 和签名类型

11. 阅读检查 ​

从客户端调用复述到 PMS:ApplicationPackageManager → IPackageManager Binder → 当前/指定 user → flags 与可见性 → PackageInfo/ResolveInfo 或异常。然后回答:为什么查询不到包不能直接证明包不存在?为什么 installExistingPackageAsUser 比完整安装轻?为什么 clearApplicationUserData 不会让包从 getInstalledPackages 消失?