APEX Native 库
本文承接 APEX 架构、ApexManager 管理 和 APEX 中的 Java 库,聚焦 APEX 内 .so 库如何被使用。核心结论是:PMS 不从 APEX 中提取 native 库,也不负责构建 linker namespace;PMS 把 active mount path 注册为包路径,apexd/linkerconfig/bionic 再完成运行时可见性。
1. 安装分叉
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:preparePackage
final boolean isApex =
(installFlags & PackageManager.INSTALL_APEX) != 0;
@ScanFlags int scanFlags =
SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (isApex) {
scanFlags |= SCAN_AS_APEX;
}
final File tmpPackageFile = new File(
isApex ? request.getApexInfo().modulePath
: request.getCodePath());APEX 分支使用 apexd 返回的 modulePath;普通 APK 使用安装请求的 code path。SCAN_AS_APEX 影响后续包解析、ABI 和代码路径处理,不能仅依据文件后缀判断。
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:native library 分支
scanFlags |= SCAN_NO_DEX;
if (!isApex) {
Pair<PackageAbiHelper.Abis,
PackageAbiHelper.NativeLibraryPaths> derivedAbi =
mPackageAbiHelper.derivePackageAbi(
parsedPackage, systemApp,
isUpdatedSystemApp,
abiOverride,
ScanPackageUtils
.getAppLib32InstallDir());
derivedAbi.first.applyTo(parsedPackage);
derivedAbi.second.applyTo(parsedPackage);
}
if (isApex) {
parsedPackage.setPath(
request.getApexInfo().modulePath);
parsedPackage.setBaseApkPath(
request.getApexInfo().modulePath);
}普通 APK 需要由 PMS 推导 ABI、native library root 和提取路径;APEX 跳过这套 derivePackageAbi,因为 .so 已在 payload 镜像中,运行时路径来自 active mount。
2. Active mount
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:ActiveApexInfo、getBackingApexFile
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);
}
public File getBackingApexFile(File file) {
Path path = file.toPath();
if (!path.startsWith(
Environment.getApexDirectory().toPath())) {
return null;
}
if (path.getNameCount() < 2) {
return null;
}
String moduleName = path.getName(1).toString();
for (ActiveApexInfo apex : getActiveApexInfos()) {
if (apex.apexModuleName.equals(moduleName)) {
return apex.apexFile;
}
}
return null;
}/apex/<module> 是挂载视图,apexFile 是实际 APEX 文件;二者不能互换。PMS 通过 active mount path 解析包和定位 native library,调试时如果只看 /data/apex/active 文件而不看 /apex/<module>,会漏掉当前运行时路径。
3. Manifest 声明
APEX manifest 中的 native library 声明由 apexd/linkerconfig 使用:provider 声明可导出的库,consumer 声明依赖,JNI 库用于 Java native loader 的 namespace 关联。PMS 的 Java 代码不解析这些字段来提取 .so;它接收扫描后的 package/path 元数据。
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:APEX ABI 跳过分支
// The native libs of Apex are located in apex_payload.img.
// They do not need to be parsed from the original apex file.
if (!isApex) {
derivePackageAbi(parsedPackage, systemApp,
isUpdatedSystemApp, abiOverride,
ScanPackageUtils.getAppLib32InstallDir());
}这个注释是 PMS/native 库边界的直接证据:APEX 的 .so 不复制到 APK 的 lib/<abi> 目录,解析器也不通过 APK zip 抽取它们。
4. linker namespace
APEX native 库是否能被某个进程加载,取决于运行时 linker namespace 和链接关系。APEX 的 provideNativeLibs、requireNativeLibs、jniLibs 由 linkerconfig 与 libnativeloader 消费;PMS 只保证 active APEX 的包路径和元数据被正确注册。
源码文件:system/apex/apexd/apex_constants.h,符号:APEX 路径常量
static constexpr const char* kApexRoot = "/apex";
static constexpr const char* kApexDataDir = "/data/apex";
static constexpr const char* kActiveApexPackagesDataDir =
"/data/apex/active";
static constexpr const char* kApexPackageSystemDir =
"/system/apex";系统分区的预装 APEX、data 分区的 active APEX 和运行时 /apex mount 是不同层次。linker namespace 使用 active mount,回滚/更新管理使用 data/apex session 和备份目录。
5. JNI 库加载
APEX 中的 Java 类调用 System.loadLibrary 时,libnativeloader 会根据 classloader 关联的 namespace 搜索对应 APEX 的 lib/lib64。jniLibs 声明决定哪些库对该 Java 运行环境可见;它不等于 provideNativeLibs 的全局导出。
运行时路径大致是:
Java classloader
-> NativeLoaderNamespace
-> linker namespace links
-> /apex/<module>/lib 或 lib64
-> dlopen(lib*.so)如果 native library 文件存在但 dlopen 失败,优先检查 namespace links、ABI、jniLibs 声明和 active mount,不要先回到 PMS 的 APK ABI 提取逻辑。
6. 路径写回
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:APEX path 设置
if (isApex) {
parsedPackage.setPath(
request.getApexInfo().modulePath);
parsedPackage.setBaseApkPath(
request.getApexInfo().modulePath);
}PMS 将 active module path 写入 parsed package 的 path/baseApkPath,使后续包查询、系统包映射和 APK-in-APEX 判断指向挂载视图。native library 的真正搜索仍由 linker 处理,但 PMS 必须先提供正确的 active path。
7. Backing file
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:getBackingApexFile
if (!path.startsWith(
Environment.getApexDirectory().toPath())) {
return null;
}
String moduleName = path.getName(1).toString();
for (ActiveApexInfo apex : getActiveApexInfos()) {
if (apex.apexModuleName.equals(moduleName)) {
return apex.apexFile;
}
}
return null;调试工具或系统服务可以把 /apex/<module>/... 文件反查到对应 .apex 文件。路径不在 /apex 根下、层级不足或 module 不在 active 列表时返回 null;这不是 native loader 的错误,而是 backing-file 查询边界。
8. 失败定位
| 现象 | 应检查 |
|---|---|
APK 的 .so 正常,APEX 的 .so 不可见 | active mount、linker namespace、provide/require |
| PMS 报 ABI/库提取错误 | 是否误走了非 APEX derivePackageAbi 分支 |
JNI loadLibrary 失败 | jniLibs、classloader namespace、ABI 路径 |
| module path 指向旧版本 | ApexInfo.modulePath、active cache、apexd 激活状态 |
| 文件能看到但跨模块加载失败 | linker namespace link,而非 Linux 文件存在性 |
| backing APEX 查询为空 | 路径是否位于 /apex/<module>,module 是否 active |
9. 阅读检查
复述这条路径:apexd 激活 payload → /apex/<module>/lib* 挂载 → PMS setPath(modulePath) → linkerconfig 建立 namespace → native process/JNI 通过 namespace 加载 .so。
然后回答:PMS 是否把 APEX .so 解压到 APK lib 目录?provideNativeLibs 是否由 PackageManagerService 直接创建 linker namespace?active mount path 和 .apex 文件路径是否等价?答案分别是“不是”“不是”“不等价”。
