Skip to content

APEX Java 库

追踪 Android 17 APEX 中 Java 库和系统服务的扫描、classpath 边界与 system_server 消费路径。

基于android-17.0.0_r1
AndroidPMSAPEXJavaClasspath

APEX Java 库 ​

本文承接 APEX 架构 和 ApexManager 管理,聚焦 APEX 对 Java 类路径的贡献。文章回答三个问题:APEX 中的 JAR 如何进入系统的 classpath 视图,APEX 声明的 system service 如何被 PMS 收集,以及为什么“APEX 被激活”不等于“任意 Java 类立即可见”。

Android 17 中,BOOTCLASSPATH/SYSTEMSERVERCLASSPATH 的最终环境由启动链和已激活 APEX 共同决定;PMS 自己只读取环境用于诊断,并通过扫描结果收集 APEX 声明的 system service。类路径上的 JAR 由运行时 classloader 消费,PMS 不把 JAR 内容读成 Java 类。

1. 三个边界 ​

对象ownerPMS 看到什么
APEX payloadapexdactive module path、版本、挂载目录
Java classpathruntime/init/classpath 配置BOOTCLASSPATH、SYSTEMSERVERCLASSPATH 环境值
APEX system servicePMS ApexManagername、jarPath、initOrder

2. Classpath 读取 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:system scan 初始化

java
final String bootClassPath =
        System.getenv("BOOTCLASSPATH");
final String systemServerClassPath =
        System.getenv("SYSTEMSERVERCLASSPATH");

if (bootClassPath == null) {
    Slog.w(TAG, "No BOOTCLASSPATH found!");
}
if (systemServerClassPath == null) {
    Slog.w(TAG,
            "No SYSTEMSERVERCLASSPATH found!");
}

PMS 在系统扫描早期读取两个环境变量,主要用于启动诊断和后续包扫描上下文。它不会在这里拼接 APEX JAR,也不会因为变量为空就自行挂载 APEX;环境的建立属于 init/apexd/runtime 启动链。

3. Service 元数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java,符号:字段与排序

java
public final class ApexSystemServiceInfo
        implements Comparable<ApexSystemServiceInfo> {
    final String mName;
    @Nullable final String mJarPath;
    final int mInitOrder;

    public int compareTo(ApexSystemServiceInfo other) {
        if (mInitOrder == other.mInitOrder) {
            return mName.compareTo(other.mName);
        }
        return -Integer.compare(mInitOrder,
                other.mInitOrder);
    }
}

ApexSystemServiceInfo 只保存 system service 的元数据,不保存 class 或实例。initOrder 越高越优先;相同顺序按名称稳定排序,避免不同设备/扫描顺序导致 system_server 启动顺序不确定。

4. 扫描结果收集 ​

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

java
for (ParsedApexSystemService service
        : scanResult.pkg.getApexSystemServices()) {
    String minSdkVersion = service.getMinSdkVersion();
    if (minSdkVersion != null
            && !UnboundedSdkLevel.isAtLeast(
                    minSdkVersion)) {
        continue;
    }
    String maxSdkVersion = service.getMaxSdkVersion();
    if (maxSdkVersion != null
            && !UnboundedSdkLevel.isAtMost(
                    maxSdkVersion)) {
        continue;
    }

    if (ai.isActive) {
        ApexSystemServiceInfo info =
                new ApexSystemServiceInfo(
                        service.getName(),
                        service.getJarPath(),
                        service.getInitOrder());
        mApexSystemServices.add(info);
    }
}
Collections.sort(mApexSystemServices);

只有 active APEX 的 system service 才进入 PMS 列表;minSdkVersion/maxSdkVersion 不满足时跳过。这个过滤是包扫描阶段的版本边界,不是 classloader 在运行时加载失败后的补救。

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:重复服务检查

java
for (ApexSystemServiceInfo info
        : mApexSystemServices) {
    if (info.getName().equals(service.getName())) {
        throw new IllegalStateException(
                TextUtils.formatSimple(
                        "Duplicate apex-system-service %s",
                        service.getName()));
    }
}

两个 active APEX 不能声明同名 system service。PMS 选择抛异常而不是按 module/version 覆盖,因为重复名称会使 system_server 的服务 owner 不明确。

5. JAR 路径的含义 ​

mJarPath 是 APEX manifest 中声明的 JAR 路径元数据,通常指向 active mount 下的文件。PMS 记录它用于后续 system service 启动;它不把路径转换为 /data/app code path,也不把 APEX Java 库当作普通应用 dexopt 目标。

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

java
public List<ApexSystemServiceInfo>
        getApexSystemServices() {
    synchronized (mLock) {
        Preconditions.checkState(
                mApexSystemServices != null,
                "APEX packages have not been scanned");
        return mApexSystemServices;
    }
}

调用必须发生在 APEX scan 完成之后。返回的是已排序列表;调用者不应在未扫描阶段自行构造 system service,避免绕过 SDK 和 duplicate 检查。

6. 扫描与加载 ​

PMS 的 APEX 扫描和 Java classloader 加载是两个阶段:

  1. apexd 激活并挂载 APEX,启动链建立 classpath 环境。
  2. PMS 读取 active APEX 信息和 package manifest,注册 package/system service 元数据。
  3. system_server 根据 classpath 和 ApexSystemServiceInfo 启动相应服务。

PMS 扫描成功只能证明元数据进入包模型;如果 JAR 不在 SYSTEMSERVERCLASSPATH,或者 module 不满足 SDK 条件,仍不能假定对应 class 可被 system_server 加载。

7. Active/factory ​

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

java
public ActiveApexInfo(ApexInfo apexInfo) {
    this(
            apexInfo.moduleName,
            new File(Environment.getApexDirectory()
                    + File.separator
                    + apexInfo.moduleName),
            new File(apexInfo.preinstalledModulePath),
            apexInfo.isFactory,
            new File(apexInfo.modulePath),
            apexInfo.activeApexChanged);
}

同一 module 可能有预装路径和当前 active module path;isFactory 区分当前是否仍使用 factory 版本,activeApexChanged 记录 active 集合是否相对上次启动发生变化。Java library 的实际可见路径应以 active module path 和 classpath 环境为准,而不是预装文件路径。

8. 更新影响 ​

staged APEX 更新在重启后改变 active mount 和 classpath 输入;PMS 下次启动重新扫描 active APEX,并重新建立 system service 列表。旧 system_server 进程不会因为一个新 .apex 文件落到 data 分区就自动获得新 class。

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java,符号:active APEX 扫描

java
final List<ApexManager.ActiveApexInfo> activeApexInfos =
        mApexManager.getActiveApexInfos();
for (ApexManager.ActiveApexInfo apexInfo
        : activeApexInfos) {
    final ScanPartition partition =
            resolveApexToScanPartition(apexInfo);
    // Scan the active module path with APEX metadata.
}

更新后 InitAppsHelper 以 active APEX 信息生成扫描 partition;因此 package model、system service metadata 和实际挂载路径在同一次启动扫描中重新对齐。

9. 失败定位 ​

现象应检查
Java 类找不到SYSTEMSERVERCLASSPATH/BOOTCLASSPATH 是否含 active APEX JAR
system service 未启动getApexSystemServices 是否因 SDK 条件跳过
APEX 已更新但旧代码仍运行是否已重启并重新扫描 active mount
两个模块声明同名 servicenotifyScanResultLocked duplicate 检查
PMS 读不到 active APEXgetActivePackages、apexservice、active cache
使用了错误 JARactive modulePath 与 preinstalled path 是否混淆

10. 阅读检查 ​

复述这条链路:apexd 激活 → classpath 环境建立 → PMS getAllApexInfos/scan → notifyScanResultLocked 过滤并收集 system service → 按 initOrder 排序 → system_server 使用 classpath 加载 JAR。

然后回答:PMS 是否负责把 APEX JAR 加入 SYSTEMSERVERCLASSPATH?ApexSystemServiceInfo 是否保存 service 实例?SDK 不匹配的 service 会在排序后再失败吗?正确答案分别是“不是,classpath 属于启动/运行时链路”“不是,只是元数据”“不会,在扫描收集阶段就跳过”。