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
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 查询
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
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 注释契约
/**
* @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 权限说明
/**
* 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,符号:单包与批量查询
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
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
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
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
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. 失败定位
| 现象 | 首先检查 |
|---|---|
NameNotFoundException | userId、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 消失?
