PackageManagerNative
PackageManagerNative 是 Package Manager 为 C/C++ 进程提供的 Binder 接口实现。它的服务端类写在 Java 中,继承 AIDL 生成的 IPackageManagerNative.Stub;native 客户端拿到 package_native 后,使用同一份 AIDL 生成的 C++ proxy 调用它。链路经过 Binder,但不经过 JNI:跨语言边界由 AIDL 的 Java/C++ 代码生成和 Parcel 编码完成。
这个接口有意保持得很窄。Android 17 的 AIDL 只包含 UID/包名映射、签名信息、UID、权限、安装来源、版本、音频回放捕获、分区位置、target SDK、调试标志、系统 feature 和 staged APEX observer。它没有把完整 PackageInfo、Intent 解析、安装会话或 Settings 状态暴露给 native。
本文沿着真实源码回答以下问题:
package_native在 PMS 启动的哪个时点注册,native 端如何发现它;- AIDL 为什么使用
@utf8InCpp、@nullable和专用PackageInfoNative,而不是直接复用 JavaPackageInfo; getNamesForUids()如何把 Javanull转成 native 可解析的空字符串;- 签名信息为何把多签名包和签名轮换历史统一填入
apkContentSigners; getInstallerForPackage()如何区分真实 installer、预加载和 sideload;getVersionCodeForPackage()、target SDK、debuggable、location flags 的失败语义为何不同;- staged APEX observer 如何从 native/ART/BinaryTransparency 侧注册,如何在 session stage/abandon 后收到回调;
- native 调用方如何处理 ServiceManager、Binder
Status、空值和异步 observer 失败。
PMS007 已讨论 Java IPackageManager,PMS008/009/010/011 分别讨论内部接口、Computer 查询、快照和 Local snapshot;本篇只在需要时引用这些层,重点放在 native Binder contract 和数据转换边界。
1. 接口定位
1.1 AIDL安全
源码文件:frameworks/base/native/android/aidl/android/content/pm/IPackageManagerNative.aidl
/**
* Parallel implementation of certain {@link PackageManager} APIs that need to
* be exposed to native code.
* <p>These APIs are a parallel definition to the APIs in PackageManager, so,
* they can technically diverge. However, it's good practice to keep these
* APIs in sync with each other.
* <p>Because these APIs are exposed to native code, it's possible they will be
* exposed to privileged components [such as UID 0]. Care should be taken to
* avoid exposing potential security holes for methods where permission checks
* are bypassed based upon UID alone.
*
* @hide
*/
interface IPackageManagerNative {AIDL 自己给出了三条边界:
- 这是
PackageManagerAPI 的 parallel definition,两个接口理论上可能漂移;新增/修改方法要同步考虑 Java 与 native 的语义。 - 它只暴露 native code 真正需要的少量能力,不是完整 PackageManager facade。
- native 调用者可能是高权限组件,包括 UID 0;如果某个方法仅凭 UID 绕过权限,必须重新评估信息泄露风险。
因此,PackageManagerNative 的安全性不能只看“调用者是 native”。真正的 caller UID 仍由 Binder 线程提供,方法是否使用这个 UID、是否根据调用 user 过滤、是否把未知包映射成默认值,都必须逐个检查。
1.2 Java服务端
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
final class PackageManagerNative extends IPackageManagerNative.Stub {
private final PackageManagerService mPm;
PackageManagerNative(PackageManagerService pm) {
mPm = pm;
}
}服务端实现是 Java package-private 类,持有一个 PackageManagerService 引用。AIDL 编译器生成的 Stub 负责 Binder transaction 解码,PackageManagerNative 负责把已解码的参数交给 PMS/Computer,并将 Java 对象转换为 AIDL parcelable。
这里没有 JNI 方法,也没有 C++ service implementation。native 端的“native”指调用方和生成的 proxy 语言,不指 PMS 服务端实现语言。
1.3 与Java入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
IPackageManagerImpl iPackageManager = m.new IPackageManagerImpl();
ServiceManager.addService("package", iPackageManager);
final PackageManagerNative pmn = new PackageManagerNative(m);
ServiceManager.addService("package_native", pmn);PMS 同时发布两个 Binder service:
| service name | server object | 主要客户端 | 数据协议 |
|---|---|---|---|
package | IPackageManagerImpl | Java framework/app facade | Java IPackageManager,大量 PackageInfo/ParceledListSlice |
package_native | PackageManagerNative | native/系统组件 | IPackageManagerNative,专用 native parcelable 和简单标量 |
两者可能都调用 snapshotComputer(),但不是同一个接口继承链,也没有共享一个 generated Stub。native 客户端不能把 package_native 当成 package 做 transaction 兼容替换。
2. AIDL 数据契约
2.1 方法集合
源码文件:frameworks/base/native/android/aidl/android/content/pm/IPackageManagerNative.aidl
@utf8InCpp String[] getNamesForUids(in int[] uids);
@nullable PackageInfoNative getPackageInfoWithSigningInfo(String packageName, int userId);
@nullable PackageInfoNative[] getPackageInfoWithSigningInfoForUid(int uid);
int getPackageUid(in @utf8InCpp String packageName, in long flags, in int userId);
int checkPermission(in String permName, in String packageName, in int userId);
@utf8InCpp String getInstallerForPackage(in String packageName);
long getVersionCodeForPackage(in String packageName);
boolean[] isAudioPlaybackCaptureAllowed(in @utf8InCpp String[] packageNames);
int getLocationFlags(in @utf8InCpp String packageName);
int getTargetSdkVersionForPackage(in String packageName);
@utf8InCpp String getModuleMetadataPackageName();
boolean hasSha256SigningCertificate(in @utf8InCpp String packageName, in byte[] certificate);
boolean isPackageDebuggable(in String packageName);
boolean hasSystemFeature(in String featureName, in int version);
void registerStagedApexObserver(in IStagedApexObserver observer);
void unregisterStagedApexObserver(in IStagedApexObserver observer);
StagedApexInfo[] getStagedApexInfos();这些方法可按返回协议分成四类:
- 数组位置一一对应:
getNamesForUids()、isAudioPlaybackCaptureAllowed();输入数量决定输出数量,未知项用空字符串或 false 表达。 - 可空对象:两个
getPackageInfoWithSigningInfo*()和 staged APEX 数组;调用方必须检查 null。 - 标量与哨兵:UID、version code、location flags、target SDK、debuggable、feature;不同方法有不同失败策略。
- 异步 observer:register/unregister 不返回 staged 状态,变化通过
IStagedApexObserver回调发送。
2.2 编码与parcelable
源码文件:frameworks/base/native/android/aidl/android/content/pm/IPackageManagerNative.aidl
/**
* Returns a set of names for the given UIDs.
* IMPORTANT: Unlike the Java version of this API, unknown UIDs are not represented by 'null's.
* Instead, they are represented by empty strings.
*/
@utf8InCpp String[] getNamesForUids(in int[] uids);@utf8InCpp 告诉 AIDL C++ backend 生成 UTF-8 string 形式的接口,native 调用方不需要把 Java String 语义映射成 UTF-16 String16 再自行转换。它只影响 generated C++ 类型/编码约定,不改变服务端 Java 字符串的来源。
PackageInfoNative、SigningInfoNative 和 SignatureNative 是专门为 native 需要设计的最小数据结构:
源码文件:frameworks/base/native/android/aidl/android/content/pm/PackageInfoNative.aidl
/**
* At present it's a very small subset, because it includes only items that have been required
* by native code, but it uses the same structure and naming as the full PackageInfo in order
* to ensure other elements can be cleanly added as necessary.
*/
parcelable PackageInfoNative {
String packageName;
@nullable SigningInfoNative signingInfo;
@nullable String sourceDir;
@nullable String[] splitSourceDirs;
}签名信息本身又由两个 native AIDL parcelable 表达:
源码文件:frameworks/base/native/android/aidl/android/content/pm/SigningInfoNative.aidl、SignatureNative.aidl
parcelable SigningInfoNative {
/**
* APK content signers. Includes current signers for multiple-signer packages,
* or signing certificate history for rotated certificates.
*/
SignatureNative[] apkContentSigners;
}
parcelable SignatureNative {
byte[] signature;
}这些 parcelable 没有 PackageInfo 的 requested permissions、组件列表、应用 flags 等字段。字段越少,native 接口越容易保持稳定,也越不容易把不必要的 framework 状态泄露给特权 native 客户端。
3. 注册与发现
3.1 PMS 注册时点
服务注册位于 PMS main() 后段:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
m.installAllowlistedSystemPackages();
IPackageManagerImpl iPackageManager = m.new IPackageManagerImpl();
ServiceManager.addService("package", iPackageManager);
final PackageManagerNative pmn = new PackageManagerNative(m);
ServiceManager.addService("package_native", pmn);
return m;服务发布发生在系统包 allowlist 安装处理之后、PackageManagerService.main() 返回之前。native 客户端如果在此之前查找 service,会得到 null 或等待失败;ServiceManager.waitForService() 和 getService() 的行为差异由客户端选择。
3.2 storaged查询
native 消费者 storaged 用 defaultServiceManager() 找到服务,再通过 generated interface cast 调用:
源码文件:system/core/storaged/storaged_uid_monitor.cpp
void get_uid_names(const vector<int>& uids,
const vector<std::string*>& uid_names) {
sp<IServiceManager> sm = defaultServiceManager();
if (sm == NULL) {
LOG(ERROR) << "defaultServiceManager failed";
return;
}
sp<IBinder> binder = sm->getService(String16("package_native"));
if (binder == NULL) {
LOG(ERROR) << "getService package_native failed";
return;
}
sp<IPackageManagerNative> package_mgr = interface_cast<IPackageManagerNative>(binder);
std::vector<std::string> names;
binder::Status status = package_mgr->getNamesForUids(uids, &names);
if (!status.isOk()) {
LOG(ERROR) << "package_native::getNamesForUids failed: "
<< status.exceptionMessage();
return;
}
}这里的失败层次有三层:ServiceManager 不可用、服务 binder 不存在、Binder 调用返回非-OK Status。native 代码没有把这些异常抛到 Java,而是记录错误并返回;上层 storaged 会继续使用已有 uid 统计,但本次名称刷新不会完成。
3.3 空字符串
storaged 只在名称非空时覆盖缓存:
源码文件:system/core/storaged/storaged_uid_monitor.cpp
for (uint32_t i = 0; i < uid_names.size(); i++) {
if (!names[i].empty()) {
*uid_names[i] = names[i];
}
}
refresh_uid_names = false;因此服务端把未知 UID 转成 "" 很重要:native caller 可以保留原来的空/未知状态,而不会把 AIDL null 解析成异常。getNamesForUids() 的输出协议不是 Java PackageManager.getNamesForUids() 的逐字复制,必须以 native AIDL 注释和服务端转换逻辑为准。
4. 包信息与签名
4.1 单包查询
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public PackageInfoNative getPackageInfoWithSigningInfo(String packageName, int userId) {
PackageInfo pInfo = mPm.snapshotComputer().getPackageInfo(packageName,
PackageManager.GET_SIGNING_CERTIFICATES, userId);
if (pInfo == null) {
return null;
}
PackageInfoNative result = new PackageInfoNative();
result.packageName = packageName;
if (BuildFlags.SUPPORT_AVF_ADVANCE_MULTITENANCY && pInfo.applicationInfo != null) {
result.sourceDir = pInfo.applicationInfo.sourceDir;
result.splitSourceDirs = pInfo.applicationInfo.splitSourceDirs;
}
if (pInfo.signingInfo == null) {
return result;
}服务端先通过 Computer 请求 GET_SIGNING_CERTIFICATES,再创建最小 PackageInfoNative。即使 pInfo.signingInfo 为 null,也会返回包含 packageName 的对象;因此 native caller 必须区分“整个结果 null”和“结果存在但 signingInfo 未设置”。sourceDir/splitSourceDirs 只有在 AVF advance multitenancy flag 和 applicationInfo 同时满足时才填入。
4.2 签名轮换转换
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
result.signingInfo = new SigningInfoNative();
Signature[] signatures = pInfo.signingInfo.hasMultipleSigners()
? pInfo.signingInfo.getApkContentsSigners()
: pInfo.signingInfo.getSigningCertificateHistory();
if (signatures == null) {
return result;
}
SignatureNative[] apkContentSigners = new SignatureNative[signatures.length];
for (int i = 0; i < signatures.length; i++) {
SignatureNative sig = new SignatureNative();
sig.signature = signatures[i].toByteArray();
apkContentSigners[i] = sig;
}
result.signingInfo.apkContentSigners = apkContentSigners;
return result;分支语义与 Java SigningInfo 一致:多 signer 包返回当前 APK 内容 signer;单 signer 且发生过证书轮换时返回 signing certificate history。native 侧统一读取 apkContentSigners,不需要理解 Java hasMultipleSigners() 和 lineage API。
这里的字段名有一个值得注意的历史兼容点:apkContentSigners 在 AIDL 中承载两种来源的数据,具体是当前 signer 还是历史 lineage,由服务端根据 hasMultipleSigners() 决定。native caller 不应把数组中的每个元素都当成“当前同时生效的 signer”。
4.3 UID展开
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public PackageInfoNative[] getPackageInfoWithSigningInfoForUid(int uid)
throws RemoteException {
String[] packageNames = mPm.snapshotComputer().getPackagesForUid(uid);
if (packageNames == null) {
return null;
}
int userId = UserHandle.getUserId(uid);
PackageInfoNative[] result = new PackageInfoNative[packageNames.length];
for (int i = 0; i < packageNames.length; i++) {
result[i] = getPackageInfoWithSigningInfo(packageNames[i], userId);
}
return result;
}UID 查询先拿一个 Computer 的 package names,再按 UID 所属 user 逐个调用单包转换。shared UID 会返回多个 package info;如果某个包在第二次查询时消失,数组对应位置可能是 null。这个方法没有把“UID 反查和签名读取”封装成一次原子事务,native caller 仍需接受 package update/uninstall 竞态。
5. 标量查询
5.1 package UID
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public int getPackageUid(String packageName, long flags, int userId) throws RemoteException {
return mPm.snapshotComputer().getPackageUid(packageName, flags, userId);
}该方法把 flags 和 userId 原样传给 Computer.getPackageUid()。失败值由 Computer/PackageManager contract 决定,AIDL 注释明确是 -1。它没有在 Native 层额外把异常改成 0 或抛 RemoteException,所以 C++ caller 应按 -1 判断“不可用”。
5.2 权限检查
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public int checkPermission(String permName, String packageName, int userId)
throws RemoteException {
return mPm.checkPermission(permName, packageName, userId);
}权限检查直接委托 PMS,而不是通过 Computer.checkUidPermission()。原因是输入是 package name + user,PMS 的 checkPermission() 还负责包归属、user 语义和权限 owner 委托。返回值沿用 PERMISSION_GRANTED/PERMISSION_DENIED,未知包的具体异常/拒绝行为要继续看 PMS 方法实现,不能仅凭 Native wrapper 推断。
5.3 安装来源
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
// NB: this differentiates between preloads and sideloads
@Override
public String getInstallerForPackage(String packageName) throws RemoteException {
final Computer snapshot = mPm.snapshotComputer();
final int callingUser = UserHandle.getUserId(Binder.getCallingUid());
final String installerName = snapshot.getInstallerPackageName(packageName, callingUser);
if (!TextUtils.isEmpty(installerName)) {
return installerName;
}
// differentiate between preload and sideload
ApplicationInfo appInfo = snapshot.getApplicationInfo(packageName,
/*flags*/ 0, /*userId*/ callingUser);
if (appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
return "preload";
}
return "";
}这个方法的身份和返回值都很具体:userId 取自真实 Binder caller,先查 installer package;没有 installer 时,如果包是 system app 返回特殊字符串 preload,否则返回空字符串代表 sideload 或 unknown。native caller 不能把空字符串当成“安装者包名为空但可继续调用 installer API”。
5.4 版本码
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public long getVersionCodeForPackage(String packageName) throws RemoteException {
try {
int callingUser = UserHandle.getUserId(Binder.getCallingUid());
PackageInfo pInfo = mPm.snapshotComputer()
.getPackageInfo(packageName, 0, callingUser);
if (pInfo != null) {
return pInfo.getLongVersionCode();
}
} catch (Exception e) {
}
return 0;
}版本码查询把异常和 null 都压成 0。AIDL 注释把 0 定义为 unknown/unknowable;服务端没有日志,也没有把原始异常传给 native。这个宽松策略适合只需要“已知版本则比较,否则按未知处理”的 native caller,但会隐藏具体失败原因。
5.5 SDK与调试
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public int getTargetSdkVersionForPackage(String packageName) throws RemoteException {
int targetSdk = mPm.snapshotComputer().getTargetSdkVersion(packageName);
if (targetSdk != -1) {
return targetSdk;
}
throw new RemoteException("Couldn't get targetSdkVersion for package " + packageName);
}
@Override
public boolean isPackageDebuggable(String packageName) throws RemoteException {
int callingUser = UserHandle.getCallingUserId();
ApplicationInfo appInfo = mPm.snapshotComputer()
.getApplicationInfo(packageName, 0, callingUser);
if (appInfo != null) {
return (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
}
throw new RemoteException("Couldn't get debug flag for package " + packageName);
}两个方法都把未知包转换为 RemoteException,与 version code 的宽松 0 不同。target SDK 先看 Computer 返回的 -1;debuggable 先按 caller user 生成 ApplicationInfo。native caller 必须按方法契约分别处理:不能为方便把所有异常都当成 0,也不能把 target SDK 0 当成有效旧版本。
5.6 音频回放捕获
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public boolean[] isAudioPlaybackCaptureAllowed(String[] packageNames)
throws RemoteException {
int callingUser = UserHandle.getUserId(Binder.getCallingUid());
final Computer snapshot = mPm.snapshotComputer();
boolean[] results = new boolean[packageNames.length];
for (int i = results.length - 1; i >= 0; --i) {
ApplicationInfo appInfo = snapshot.getApplicationInfo(packageNames[i], 0, callingUser);
results[i] = appInfo != null && appInfo.isAudioPlaybackCaptureAllowed();
}
return results;
}输出长度与输入相同;未知或不可见包对应 false。服务端只获取一次 Computer,再逐包读取 ApplicationInfo,因此同一批输入使用一个 PMS 查询视图,但每个 package 仍可能因为过滤而返回 null。native caller 应按输入索引读取结果,不要压缩 false 项。
5.7 分区位置 flags
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java、IPackageManagerNative.aidl
@Override
public int getLocationFlags(String packageName) throws RemoteException {
int callingUser = UserHandle.getUserId(Binder.getCallingUid());
ApplicationInfo appInfo = mPm.snapshotComputer().getApplicationInfo(packageName,
/*flags*/ 0, /*userId*/ callingUser);
if (appInfo == null) {
throw new RemoteException("Couldn't get ApplicationInfo for package " + packageName);
}
return ((appInfo.isSystemApp() ? IPackageManagerNative.LOCATION_SYSTEM : 0)
| (appInfo.isVendor() ? IPackageManagerNative.LOCATION_VENDOR : 0)
| (appInfo.isProduct() ? IPackageManagerNative.LOCATION_PRODUCT : 0));
}位置常量定义在同一份 AIDL 中,native proxy 和 Java Stub 使用同一组 bit 值:
const int LOCATION_SYSTEM = 0x1;
const int LOCATION_VENDOR = 0x2;
const int LOCATION_PRODUCT = 0x4;位置 flags 不是从 APK 路径字符串做前缀匹配,而是从 ApplicationInfo 的 isSystemApp()、isVendor()、isProduct() 计算。一个包可以同时带多个 bit;ApplicationInfo 不存在时抛 RemoteException,而不是返回 0。native 侧应把 0 理解为“存在但不属于这些分区”,而非未知包。
5.8 Feature与元数据
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public String getModuleMetadataPackageName() throws RemoteException {
return mPm.getModuleMetadataPackageName();
}
@Override
public boolean hasSystemFeature(String featureName, int version) {
return mPm.hasSystemFeature(featureName, version);
}这两个方法没有显式 snapshot 参数。模块元数据包名和 available feature 属于 PMS 的系统配置查询,服务端直接委托 PMS;是否有权限或异常由 PMS 的具体实现决定。native caller 不应把它们与 package state snapshot 查询混成一个原子视图。
5.9 SHA-256 证书
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public boolean hasSha256SigningCertificate(String packageName, byte[] certificate)
throws RemoteException {
return mPm.snapshotComputer()
.hasSigningCertificate(packageName, certificate, CERT_INPUT_SHA256);
}Native API 固定使用 CERT_INPUT_SHA256,调用方不需要传 Java PackageManager.CERT_INPUT_RAW_X509 等类型。返回 false 可能表示包不存在、不可见或证书不匹配,Native wrapper 不把原因区分开;需要诊断时必须结合更高层日志或 Java PMS 路径。
6. Staged APEX
6.1 APEX代理
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerNative.java
@Override
public void registerStagedApexObserver(IStagedApexObserver observer) {
mPm.mInstallerService.getStagingManager().registerStagedApexObserver(observer);
}
@Override
public void unregisterStagedApexObserver(IStagedApexObserver observer) {
mPm.mInstallerService.getStagingManager().unregisterStagedApexObserver(observer);
}
@Override
public StagedApexInfo[] getStagedApexInfos() {
return mPm.mInstallerService.getStagingManager().getStagedApexInfos().toArray(
new StagedApexInfo[0]);
}PackageManagerNative 不保存 observer 集合,也不实现 staged session 状态机;它把注册、注销和当前 staged info 查询转给 StagingManager。因此 observer 的去重、死亡 binder、回调线程和 session abandon 行为要进入 StagingManager 继续阅读。
6.2 ART 侧注册
源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java
static void registerForStagedApexUpdates(@NonNull ArtManagerLocal artManager) {
IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface(
ServiceManager.getService("package_native"));
if (packageNative == null) {
Log.e(TAG, "No IPackageManagerNative");
return;
}
try {
packageNative.registerStagedApexObserver(new StagedApexObserver(artManager));
} catch (RemoteException e) {
Log.e(TAG, "Failed to register staged apex observer", e);
}
}
private static class StagedApexObserver extends IStagedApexObserver.Stub {
private final @NonNull ArtManagerLocal mArtManager;
@Override
public void onApexStaged(@NonNull ApexStagedEvent event) {
mArtManager.onApexStaged(Arrays.stream(event.stagedApexInfos)
.map(info -> info.moduleName).toArray(String[]::new));
}
}ART 注册失败只记录日志并返回;成功后,observer 收到 ApexStagedEvent,只提取 module name 交给 ArtManagerLocal。它没有在回调中执行 dexopt,而是把 staged module 变化转成 ART 自己的后续工作。
6.3 B透明注册
源码文件:frameworks/base/services/core/java/com/android/server/BinaryTransparencyService.java
private void registerStagedApexUpdateObserver() {
Slog.d(TAG, "Registering APEX updates...");
IPackageManagerNative iPackageManagerNative = IPackageManagerNative.Stub.asInterface(
ServiceManager.getService("package_native"));
if (iPackageManagerNative == null) {
Slog.e(TAG, "IPackageManagerNative is null");
return;
}
try {
iPackageManagerNative.registerStagedApexObserver(new IStagedApexObserver.Stub() {
@Override
public void onApexStaged(ApexStagedEvent event) throws RemoteException {
Slog.d(TAG, "A new APEX has been staged for update. There are currently "
+ event.stagedApexInfos.length + " APEX(s) staged for update. "
+ "Scheduling measurement...");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
}
});
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register a StagedApexObserver.");
}
}BinaryTransparency 与 ART 注册的是两个独立 observer。APEX stage 事件到来时,BinaryTransparency 只调度 measurement job,不在 Binder callback 中完成测量。注册失败不会阻止服务继续运行,但 staged update 的 measurement 不会被这个 observer 触发。
6.4 集成测试
测试文件:frameworks/base/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@Test
public void testGetStagedApexInfos() throws Exception {
// Not found before staging
StagedApexInfo[] result = getPackageManagerNative().getStagedApexInfos();
assertThat(findStagedApexInfo(result, TEST_APEX_PACKAGE_NAME)).isNull();
// Stage an apex
int sessionId = Install.single(TEST_APEX_CLASSPATH).setStaged().commit();
// Query proper module name
result = getPackageManagerNative().getStagedApexInfos();
StagedApexInfo found = findStagedApexInfo(result, TEST_APEX_PACKAGE_NAME);
assertThat(found).isNotNull();
assertThat(found.hasClassPathJars).isTrue();
InstallUtils.openPackageInstallerSession(sessionId).abandon();
}测试输入是一个 staged APEX 安装包。动作分三步:stage 前查询、提交 staged session 后查询、最后 abandon session。断言证明 staged APEX 在提交后出现在 Native API 返回数组中,并且 hasClassPathJars 字段正确;它没有证明设备重启后的 APEX 激活,也没有证明 observer 回调。
6.5 异步回调
测试文件:frameworks/base/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@Test
public void testStagedApexObserver() throws Exception {
MockStagedApexObserver realObserver = new MockStagedApexObserver();
IStagedApexObserver observer = spy(realObserver);
getPackageManagerNative().registerStagedApexObserver(observer);
// Stage an apex and verify observer was called
int sessionId = Install.single(APEX_V2).setStaged().commit();
ArgumentCaptor<ApexStagedEvent> captor = ArgumentCaptor.forClass(ApexStagedEvent.class);
verify(observer, timeout(5000)).onApexStaged(captor.capture());
StagedApexInfo found = findStagedApexInfo(
captor.getValue().stagedApexInfos, SHIM_APEX_PACKAGE_NAME);
assertThat(found).isNotNull();
// Abandon and verify observer is called
Mockito.clearInvocations(observer);
InstallUtils.openPackageInstallerSession(sessionId).abandon();
verify(observer, timeout(5000)).onApexStaged(captor.capture());
assertThat(captor.getValue().stagedApexInfos).hasLength(0);
}这个测试明确证明异步契约:注册 observer 后,stage commit 会在五秒等待窗口内回调并包含目标 APEX;abandon 后再次回调,数组长度变为 0。它没有证明回调一定在哪个线程执行、Binder death 后 observer 是否自动清理,也没有证明所有非 staged APEX 更新都通过这个 observer 发送。
7. 失败与安全边界
7.1 Unknown UID
getNamesForUids() 会把 Java null entries 转为 "";native storaged 保留对应旧名称。这个行为适用于批量统计刷新,但不等于 UID 永远不存在:package 正在移除、adoptable storage 不可用或 snapshot 与底层结构短暂不一致,都可能产生未知结果。
7.2 Unknown package
Native API 对未知包有三种代表性处理:
| 方法 | 服务端结果 | native 侧含义 |
|---|---|---|
getVersionCodeForPackage | 0 | unknown/unknowable,异常被吞掉 |
getTargetSdkVersionForPackage | RemoteException | 调用失败,不能继续使用结果 |
isPackageDebuggable / getLocationFlags | RemoteException | ApplicationInfo 不存在或不可见 |
isAudioPlaybackCaptureAllowed | 对应位置 false | 与输入索引保持一致 |
getPackageInfoWithSigningInfo | null | 没有可返回的 PackageInfoNative |
调用方必须按方法逐一处理,不能写一个统一的“失败转 0”适配器。
7.3 Caller user
getInstallerForPackage()、getVersionCodeForPackage()、isPackageDebuggable()、getLocationFlags() 和音频捕获查询都从 Binder.getCallingUid()/getCallingUserId() 派生 user。native 进程如果运行在 system UID 或 root,得到的是该 UID 的 user context;服务端没有为 native caller 自动选择“所有用户”。
7.4 权限边界
IPackageManagerNative.aidl 警告某些 native 组件可能高权限,但 PackageManagerNative 的不同方法使用不同 PMS/Computer 路径:有的按 caller user 过滤,有的固定通过 PMS 系统配置,有的直接委托 staged manager。阅读安全性时必须追到具体方法和 Computer owner,不能看到 Native 就默认“绕过权限”,也不能看到 Binder 就默认“所有方法都做完整 permission check”。
7.5 注册失败
ART 和 BinaryTransparency 的注册代码都检查 null service 并捕获 RemoteException,失败后记录日志并继续。这个降级意味着 staged APEX 仍可能被安装,但对应 ART measurement 或 binary transparency measurement 不会由该 observer 触发。observer 注册不是 staged session commit 的前置条件。
8. 构建与生成
8.1 AIDL编译
源码文件:frameworks/base/native/android/Android.bp
cc_library {
name: "libandroid",
unstable: true,
local_include_dir: "aidl",
host_supported: true,
srcs: [
"aidl/android/content/pm/ApexStagedEvent.aidl",
"aidl/android/content/pm/IPackageManagerNative.aidl",
"aidl/android/content/pm/IStagedApexObserver.aidl",
"aidl/android/content/pm/PackageInfoNative.aidl",
"aidl/android/content/pm/SignatureNative.aidl",
"aidl/android/content/pm/SigningInfoNative.aidl",
"aidl/android/content/pm/StagedApexInfo.aidl",
],
}这些 AIDL 进入 libandroid 的 native library 生成 C++ interface/header 和 parcelable 支持。AIDL 的 unstable: true 表明它不是稳定 AIDL 的 VINTF version/hash contract;它适用于 platform 内部同版本组件,接口变更需要同步平台代码。
8.2 生成边界
服务端 Java 的 IPackageManagerNative.Stub 和 native C++ 的 IPackageManagerNative 都由同一 AIDL 生成。手写源码中能直接核对的是:
- Java server override 的参数和返回转换;
- C++ caller 获取 service、调用 proxy 和处理
binder::Status; - AIDL parcelable 字段及注释。
不能凭记忆补写 generated onTransact() switch、transaction code 或 C++ Parcel 字节布局;这些内容由当前构建版本生成,除非实际打开生成文件,否则不应写进教材结论。
9. 测试与证明范围
9.1 UID 名称测试
storaged_uid_monitor.cpp 的调用路径适合做 native 集成测试:
- 输入一组有效 UID 和未知 UID;
- 通过
package_native调用getNamesForUids(); - 断言输出 vector 长度与输入相同;
- 断言未知 UID 对应空字符串,valid UID 对应非空 package/shared name;
- 模拟 service 不存在或 Binder status error,断言调用方保留旧缓存且不把错误结果写入名称数组。
这证明 ServiceManager、AIDL proxy、Java wrapper 和 native 空字符串 contract,不能证明 PackageManagerService 内部 package removal 与 snapshot race 的所有时序。
9.2 包信息测试
测试输入应覆盖:普通单 signer、签名轮换、multiple signer、未知包和 signingInfo null。断言应分别检查:
packageName始终填入请求包名(当整体结果非 null);apkContentSigners对 multiple signer 使用当前 signer;- 轮换包使用 signing certificate history;
- 未知包返回 null;
- AVF flag 关闭时 sourceDir/splitSourceDirs 不被填充。
仅断言 PackageInfoNative != null 不足以证明签名转换正确,因为最小对象即使没有 signingInfo 也可能非 null。
9.3 staged APEX 测试
已有 staged install 集成测试证明了两类行为:
getStagedApexInfos()在 stage commit 后返回目标 APEX,abandon 后目标消失;- observer 在 stage 和 abandon 后异步回调,回调事件中的列表分别包含目标和为空。
它们没有证明设备重启后的 activation、非 staged APEX 的广播路径、observer binder death 清理、ART measurement job 最终成功。这些需要 StagingManager、PackageInstaller、ART 和 BinaryTransparency 各自的更高层测试。
10. 阅读路线
阅读 PackageManagerNative 的新方法时,按下面顺序建立证据:
- 先看
IPackageManagerNative.aidl的 Javadoc,记录未知值、nullable 和 caller 安全提醒。 - 在 Java Stub 实现中确认它调用
snapshotComputer()、PMS 还是StagingManager。 - 对 snapshot 查询记录 caller user、flags 和 Computer 的失败值;对 PMS 直接委托继续追权限/状态 owner。
- 对返回对象检查是否做字段裁剪、null 转换、数组位置保持和签名轮换转换。
- 反向搜索 native caller,确认它如何获取
package_native、处理binder::Status、空字符串、RemoteException 和异步回调。 - 对 observer 继续追 register → stage/abandon → callback → 客户端后续任务,不能停在注册成功。
- 找对应测试,分开标注同步查询、数据转换、竞态和异步证明范围。
- 不要凭记忆补 generated Stub/proxy 的 transaction code 或 Parcel 细节,除非当前版本生成文件已经被实际检查。
小结
Android 17 的 PackageManagerNative 是一个窄而明确的跨语言 Binder 边界:
- 服务端是 Java
PackageManagerNative,native 端通过 AIDL 生成的 C++ proxy 调用;链路走 Binder,不走 JNI。 IPackageManagerNative只暴露 native 真正需要的少量查询和 staged APEX observer,专用PackageInfoNative只包含包名、签名和必要路径。getNamesForUids()用空字符串表达未知 UID,getVersionCodeForPackage()用 0 表达未知,target SDK/debuggable/location 则抛RemoteException;失败契约必须逐方法阅读。- 包信息查询使用 Computer snapshot,但 UID 到 package 再到签名是两阶段读取,更新/卸载竞态仍然存在。
- 签名转换把 multiple signer 的当前 signer 和单 signer 的轮换历史统一放入
apkContentSigners,native 不需要复制 JavaSigningInfo的判断逻辑。 - installer 查询区分真实 installer、preload 和 sideload;位置 flags 从 ApplicationInfo 分区属性计算,而不是字符串路径猜测。
- staged APEX observer 由 StagingManager 持有,ART 和 BinaryTransparency 是独立消费者;stage、abandon 都可能触发异步回调。
- AIDL 生成代码、ServiceManager 发现、Binder Status、空值转换、snapshot 查询和异步 observer 必须作为一条完整主线阅读,不能只看 Java wrapper 的方法名。
下一篇 PMS015 将转向 dumpsys package,把本系列已经读过的 Settings、PackageState、Computer、权限、安装和 staged APEX 状态映射到实际 dump 输出,重点讲如何用命令验证源码推断,而不是做命令参数速查表。
