Skip to content

APEX 架构

追踪 Android 17 APEX 的容器边界、apexd 会话、PMS 扫描注册和运行时挂载信息。

基于android-17.0.0_r1
AndroidPMSAPEXMainlineapexd

APEX 架构 ​

本文是 APEX 模块的入口,承接 Package Manager 的扫描和安装文章,回答一个架构问题:.apex 文件如何从 apexd 的激活状态进入 PMS 的包模型,并与 APK、APEX 内嵌 APK 区分开。本文不展开 staged 安装回滚、APEX 中 Java 库或 native 库的具体链接细节,这些由后续专题负责。

APEX 不是“另一种 APK 后缀”。它由 apexd 负责激活/挂载,由 PMS 负责扫描其 Android package 元数据并注册到包模型;APEX 内部还可以包含 APK,这些 APK 由 PMS 以 SCAN_AS_APK_IN_APEX 路径单独处理。

1. 三层边界 ​

层owner事实
容器/激活apexd / IApexService选择 active APEX、校验会话、挂载 payload
包扫描PMS ApexManager / InstallPackageHelper把 active/factory APEX 转成 AndroidPackage
组件消费PMS PackageSetting、classpath、系统服务查询 APEX、处理 APK-in-APEX 和系统模块

2. 容器与 payload ​

APEX 外层文件是可签名的容器,关键元数据包括 APEX manifest、Android manifest、payload 镜像和公钥/签名材料。本文后续只依赖 PMS/apexd 的接口结果,不把 ZIP 内部每个文件名都当成 Java 层稳定 API。

源码文件:system/apex/apexd/apexd.cpp,符号:APEX 激活和 staged session 相关服务入口

cpp
// apexd owns staged-session state and activation.
// PMS calls through IApexService rather than mounting payload itself.
Result<void> UnstagePackages(const std::vector<std::string>& paths) {
  LOG(DEBUG) << "UnstagePackages() for " << Join(paths, ',');
  return ...;
}

apexd 的 owner 是容器生命周期和挂载,不是 PMS 的 package resolver。APEX 是否 active、staged session 是否 ready/successful,都必须通过 IApexService/ApexManager 交互读取。

3. ApexManager 门面 ​

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

java
public abstract class ApexManager {
    public static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
    static final int MATCH_FACTORY_PACKAGE = 1 << 1;

    private static final Singleton<ApexManager>
            sApexManagerSingleton = new Singleton<>() {
        @Override
        protected ApexManager create() {
            return new ApexManagerImpl();
        }
    };

    public static ApexManager getInstance() {
        return sApexManagerSingleton.get();
    }

    public static class ActiveApexInfo {
        @Nullable public final String apexModuleName;
        public final File apexDirectory;
        public final File preInstalledApexPath;
        public final boolean isFactory;
        public final File apexFile;
        public final boolean activeApexChanged;
    }
}

PMS 通过 ApexManager 访问 active/factory APEX 信息。ActiveApexInfo 同时保留 module name、挂载目录、预装路径、当前 apex file 和 active 是否变化;这些字段用于后续扫描、更新和系统分区映射。

4. PMS 启动扫描 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java,符号:scanApexPackagesTraced、initNonSystemApex

java
private List<ApexManager.ScanResult>
        scanApexPackagesTraced(
                PackageParser2 packageParser) {
    Trace.traceBegin(
            TRACE_TAG_PACKAGE_MANAGER,
            "scanApexPackages");
    try {
        return mInstallPackageHelper.scanApexPackages(
                mApexManager.getAllApexInfos(),
                packageParser);
    } finally {
        Trace.traceEnd(
                TRACE_TAG_PACKAGE_MANAGER);
    }
}

List<ApexManager.ScanResult> results =
        scanApexPackagesTraced(packageParser);
mApexManager.notifyScanResult(results);

扫描先从 ApexManager 获取 apexd 已知 APEX,再由 InstallPackageHelper 解析包信息,最后把扫描结果回写给 ApexManager。PMS 不从 /apex 目录盲扫来决定 active 集合,目录信息必须与 apexd 的状态对齐。

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java,符号:active APEX 与 APK-in-APEX 映射

java
for (ApexManager.ActiveApexInfo apexInfo
        : mApexManager.getActiveApexInfos()) {
    String apexPackageName =
            mApexManager.getActivePackageNameForApexModuleName(
                    apexInfo.apexModuleName);
    for (String packageName
            : mApexManager.getApksInApex(
                    apexPackageName)) {
        apkInApexPreInstalledPaths.put(
                packageName,
                apexInfo.preInstalledApexPath);
    }
}

APEX 内嵌 APK 需要建立“APK package → preinstalled APEX path”的映射,之后 PMS 才能把 APK-in-APEX 作为特殊扫描输入。它们的 package name 不等于外层 APEX module name,不能在查询时混用。

5. 扫描差异 ​

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

java
static final int SCAN_AS_APK_IN_APEX = 1 << 23;
static final int SCAN_AS_APEX = 1 << 26;

SCAN_AS_APEX 表示外层 APEX package;SCAN_AS_APK_IN_APEX 表示 APEX payload 内的 APK。两个 flag 会改变解析来源、安装位置、签名/版本关联和后续系统包处理。

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java,符号:ScanParams.forApkInApexScan

java
static ScanParams forApkInApexScan(
        File scanDir, int parseFlags, int scanFlags,
        ApexManager.ActiveApexInfo apexInfo) {
    return new ScanParams(scanDir,
            parseFlags | PARSE_APK_IN_APEX,
            scanFlags, apexInfo);
}

APK-in-APEX 使用专门 parse flag,同时携带外层 ActiveApexInfo。这让 PMS 能区分 APK 自己的 code path 与外层 APEX 的激活来源。

6. Staged session ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:staged session 抽象方法

java
abstract ApexInfoList submitStagedSession(
        ApexSessionParams params)
        throws PackageManagerException;

abstract void markStagedSessionReady(int sessionId)
        throws PackageManagerException;

abstract void markStagedSessionSuccessful(int sessionId);

abstract boolean abortStagedSession(int sessionId);

提交 staged session、标记 ready、标记 successful 和 abort 是不同状态转换。成功提交只表示 apexd 接受并完成初始验证,不表示下次启动一定激活;调用方还要显式 markStagedSessionReady。

7. Active 查询 ​

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

java
public abstract List<ActiveApexInfo>
        getActiveApexInfos();

@Nullable
public abstract String
        getActiveApexPackageNameContainingPackage(
                @NonNull String containedPackageName);

@Nullable
abstract ApexSessionInfo getStagedSessionInfo(
        int sessionId);

@NonNull
abstract SparseArray<ApexSessionInfo> getSessions();

查询 active APEX、查找某个 APK 所属 APEX 和读取 staged session 是不同 API。PMS 的包查询如果需要判断某 APK 是否来自 APEX,应该使用 getActiveApexPackageNameContainingPackage,而不是通过路径字符串猜测。

8. 包模型影响 ​

APEX 扫描结果最终进入 PMS 的 AndroidPackage/PackageSetting 集合,但其 active/factory 来源、模块名和系统分区路径仍由 APEX 专有字段维护。外层 APEX 通常不是面向普通用户的 per-user 安装包;PMS 的 user state 不能替代 apexd 的 active 状态。

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:APEX 查询

java
List<ApexManager.ActiveApexInfo> apexes =
        mApexManager.getActiveApexInfos();
for (ApexManager.ActiveApexInfo apex : apexes) {
    // Use active module path and factory metadata
    // when constructing package/system mappings.
}

包查询看到的是 PMS 注册后的 package;激活状态变化则来自 apexd session 和重启/更新流程。两种状态不同步时,应先检查 apexd session,再检查 PMS scan result。

9. 失败路径 ​

阶段失败owner/结果
staged submitapexd 初始验证失败submitStagedSession 抛 PackageManagerException
ready 标记session 不能进入下次启动markStagedSessionReady 失败
启动扫描APEX info 与解析结果不一致PMS 扫描/注册失败
APK-in-APEX外层映射缺失不应按普通 APK 路径注册
active 查询module/package 关系不存在返回 null 或空集合
回滚active session 放弃失败abort/revert 返回 false,设备状态需进一步检查

APEX 的“安装成功”至少包含多个屏障:容器被 apexd 接受、session ready、重启后激活、PMS 扫描注册。任何一个屏障失败,都不能只根据 APK 文件是否存在判断模块可用。

10. 阅读检查 ​

沿源码复述:ApexManager.getAllApexInfos → InstallPackageHelper.scanApexPackages → ApexManager.notifyScanResult → PMS 注册 APEX 与 APK-in-APEX;更新路径则是 submitStagedSession → markStagedSessionReady → 重启激活 → PMS 再次扫描。

然后回答:APEX payload 是由 PMS 挂载吗?submitStagedSession 成功是否立即激活?APK-in-APEX 与外层 APEX 是否使用同一个 scan flag?正确答案分别是“不是,由 apexd 负责”“不是,还需要 ready/激活屏障”“不是,分别使用 SCAN_AS_APEX 和 SCAN_AS_APK_IN_APEX 语义”。