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. 三个边界
| 对象 | owner | PMS 看到什么 |
|---|---|---|
| APEX payload | apexd | active module path、版本、挂载目录 |
| Java classpath | runtime/init/classpath 配置 | BOOTCLASSPATH、SYSTEMSERVERCLASSPATH 环境值 |
| APEX system service | PMS ApexManager | name、jarPath、initOrder |
2. Classpath 读取
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:system scan 初始化
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,符号:字段与排序
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
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,符号:重复服务检查
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
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 加载是两个阶段:
- apexd 激活并挂载 APEX,启动链建立 classpath 环境。
- PMS 读取 active APEX 信息和 package manifest,注册 package/system service 元数据。
- 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
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 扫描
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 |
| 两个模块声明同名 service | notifyScanResultLocked duplicate 检查 |
| PMS 读不到 active APEX | getActivePackages、apexservice、active cache |
| 使用了错误 JAR | active 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 属于启动/运行时链路”“不是,只是元数据”“不会,在扫描收集阶段就跳过”。
