Skip to content

PackageManagerNative

解析 IPackageManagerNative 的窄接口、Java Stub、native Binder proxy、包信息转换和 staged APEX 观察者。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageManagerNativeAIDLNative源码阅读

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,而不是直接复用 Java PackageInfo;
  • getNamesForUids() 如何把 Java null 转成 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

java
/**
 * 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 自己给出了三条边界:

  1. 这是 PackageManager API 的 parallel definition,两个接口理论上可能漂移;新增/修改方法要同步考虑 Java 与 native 的语义。
  2. 它只暴露 native code 真正需要的少量能力,不是完整 PackageManager facade。
  3. 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

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

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 nameserver object主要客户端数据协议
packageIPackageManagerImplJava framework/app facadeJava IPackageManager,大量 PackageInfo/ParceledListSlice
package_nativePackageManagerNativenative/系统组件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

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

java
/**
 * 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

java
/**
 * 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

java
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

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

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

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

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

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

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

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

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

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

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

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

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

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

java
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

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

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

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

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

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

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

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 侧含义
getVersionCodeForPackage0unknown/unknowable,异常被吞掉
getTargetSdkVersionForPackageRemoteException调用失败,不能继续使用结果
isPackageDebuggable / getLocationFlagsRemoteExceptionApplicationInfo 不存在或不可见
isAudioPlaybackCaptureAllowed对应位置 false与输入索引保持一致
getPackageInfoWithSigningInfonull没有可返回的 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

make
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 集成测试:

  1. 输入一组有效 UID 和未知 UID;
  2. 通过 package_native 调用 getNamesForUids();
  3. 断言输出 vector 长度与输入相同;
  4. 断言未知 UID 对应空字符串,valid UID 对应非空 package/shared name;
  5. 模拟 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 的新方法时,按下面顺序建立证据:

  1. 先看 IPackageManagerNative.aidl 的 Javadoc,记录未知值、nullable 和 caller 安全提醒。
  2. 在 Java Stub 实现中确认它调用 snapshotComputer()、PMS 还是 StagingManager。
  3. 对 snapshot 查询记录 caller user、flags 和 Computer 的失败值;对 PMS 直接委托继续追权限/状态 owner。
  4. 对返回对象检查是否做字段裁剪、null 转换、数组位置保持和签名轮换转换。
  5. 反向搜索 native caller,确认它如何获取 package_native、处理 binder::Status、空字符串、RemoteException 和异步回调。
  6. 对 observer 继续追 register → stage/abandon → callback → 客户端后续任务,不能停在注册成功。
  7. 找对应测试,分开标注同步查询、数据转换、竞态和异步证明范围。
  8. 不要凭记忆补 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 不需要复制 Java SigningInfo 的判断逻辑。
  • 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 输出,重点讲如何用命令验证源码推断,而不是做命令参数速查表。