Skip to content

APEX 安装更新

追踪 Android 17 APEX 安装会话、提交验证、ready 屏障、重启激活和 PMS 注册路径。

基于android-17.0.0_r1
AndroidPMSAPEXapexdPackageInstaller

APEX 安装更新 ​

本文承接 APEX 架构 与 ApexManager 管理,专门解释一次 APEX 更新如何从 PackageInstallerSession 进入 apexd,再回到 PMS 的扫描/注册路径。重点是状态屏障和失败语义,不重复 APK 安装或 APEX 容器格式。

APEX 更新与普通 APK 更新的根本差异是生效时机:APK 可以在当前用户安装提交后生效;staged APEX 先由 apexd 验证并保存会话,只有 markStagedSessionReady 后才会在下一次启动进入激活流程。PMS 不直接挂载 payload,也不能把“session 提交成功”当成“模块已运行”。

1. 两种安装模式 ​

模式apexd API生效时机主要约束
stagedsubmitStagedSession → markStagedSessionReady重启后可包含多个 APEX,需会话状态持久化
non-stagedinstallAndActivatePackage当前运行期单 APEX、仅支持允许热更新的模块/调试场景

2. Session 类型判断 ​

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

java
public boolean isApexSession() {
    return (params.installFlags
            & PackageManager.INSTALL_APEX) != 0;
}

INSTALL_APEX 把 PackageInstaller session 分成 APEX 与 APK 分支。后续验证、文件数量、目标路径和提交处理都会读取这个判断;它不是根据文件扩展名推断。

3. Session 校验 ​

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

java
private void validateApexInstallLocked()
        throws PackageManagerException {
    final List<File> addedFiles =
            getAddedApksLocked();
    if (addedFiles.isEmpty()) {
        throw new PackageManagerException(
                INSTALL_FAILED_INVALID_APK,
                "No packages staged");
    }
    if (addedFiles.size() > 1) {
        throw new PackageManagerException(
                INSTALL_FAILED_INVALID_APK,
                "Too many files for apex install");
    }

    File addedFile = addedFiles.get(0);
    String sourceName = addedFile.getName();
    String targetName = sourceName.endsWith(".apex")
            ? sourceName : sourceName + ".apex";
    if (!FileUtils.isValidExtFilename(targetName)) {
        throw new PackageManagerException(
                INSTALL_FAILED_INVALID_APK,
                "Invalid filename: " + targetName);
    }
    File targetFile = new File(stageDir, targetName);
    resolveAndStageFileLocked(addedFile, targetFile,
            null, List.of());
    mResolvedBaseFile = targetFile;

一个 APEX session 只能有一个 APEX 文件;session 先把它移动到 stage 目录,再把它作为 resolved base file。这里只是文件/session 合法性,不是 apexd 对 payload、AVB、版本和 VINTF 的完整验证。

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java,符号:继续解析 APEX 清单

java
ParseResult<ApkLite> ret =
        ApkLiteParseUtils.parseApkLite(
                input.reset(), mResolvedBaseFile,
                ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
if (ret.isError()) {
    throw new PackageManagerException(
            ret.getErrorCode(), ret.getErrorMessage(),
            ret.getException());
}

ApkLite apk = ret.getResult();
mPackageName = apk.getPackageName();
mVersionCode = apk.getLongVersionCode();
mSigningDetails = apk.getSigningDetails();

PMS 仍会解析 APEX 外层 APK 容器的 package name、version 和 signing details,供 session 一致性与后续包模型使用;这一步不等于 apexd 已经激活 payload。

4. Staged 提交 ​

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

java
private void onVerificationComplete() {
    if (isStaged()) {
        mStagingManager.commitSession(mStagedSession);
        sendUpdateToRemoteStatusReceiver(
                INSTALL_SUCCEEDED, "Session staged",
                null, false /* forPreapproval */);
        return;
    }
    install();
}

staged session 的验证完成后进入 StagingManager.commitSession,不会直接走普通 install()。客户端收到 “Session staged” 只说明 session 已交给 staged 管线,不能理解为 APEX 已经激活。

5. apexd 提交验证 ​

源码文件:system/apex/apexd/apexd.cpp,符号:SubmitStagedSession

cpp
Result<std::vector<ApexFile>> SubmitStagedSession(
    const int session_id,
    const std::vector<int>& child_session_ids,
    const bool has_rollback_enabled,
    const bool is_rollback,
    const int rollback_id) {
  auto install_guard = std::scoped_lock{gInstallLock};
  OR_RETURN(BackupActiveApexes());

  auto ret = OR_RETURN(OpenApexFilesInSessionDirs(
      session_id, child_session_ids));
  auto result = OR_RETURN(VerifyPackagesStagedInstall(ret));

  auto session = gSessionManager->CreateSession(session_id);
  (*session).SetChildSessionIds(child_session_ids);
  (*session).SetHasRollbackEnabled(has_rollback_enabled);
  (*session).SetIsRollback(is_rollback);
  (*session).SetRollbackId(rollback_id);
  for (const auto& apex_file : ret) {
    session->AddApexName(
        apex_file.GetManifest().name());
  }
  OR_RETURN((*session).UpdateStateAndCommit(
      SessionState::VERIFIED));
  return ret;
}

apexd 提交时先备份 active APEX,再打开 session 目录、执行 staged verification、记录 APEX module names 和 rollback 信息,最后把 session 持久化为 VERIFIED。VERIFIED 还不是下次启动会激活的状态。

源码文件:system/apex/apexd/apexd.cpp,符号:VerifyPackagesStagedInstall

cpp
Result<VerificationResult> VerifyPackagesStagedInstall(
    const std::vector<ApexFile>& apex_files) {
  for (const auto& apex_file : apex_files) {
    OR_RETURN(VerifyPackageBoot(apex_file));
  }
  OR_RETURN(VerifyNoOverlapInSessions(
      apex_files, gSessionManager->GetSessions()));
  return RunVerifyFnInsideTempMounts(
      all_apex_files, check_fn);
}

验证可能包括临时挂载、dm-verity 读取、版本/签名兼容、与已有 staged sessions 的 overlap 检查以及 VINTF 检查。PMS 不应把这一步压缩成“检查文件存在”。

6. Ready 屏障 ​

源码文件:system/apex/apexd/apexd.cpp,符号:MarkStagedSessionReady

cpp
Result<void> MarkStagedSessionReady(
    const int session_id) {
  auto install_guard = std::scoped_lock{gInstallLock};
  auto session = gSessionManager->GetSession(session_id);
  if (!session.ok()) {
    return session.error();
  }
  auto state = (*session).GetState();
  if (state == SessionState::STAGED) {
    return {};
  }
  if (state == SessionState::VERIFIED) {
    return (*session).UpdateStateAndCommit(
        SessionState::STAGED);
  }
  return Error() << "Invalid state for session "
                 << session_id;
}

只有 VERIFIED 才能变成 STAGED;STAGED 重复调用是幂等的,其他状态拒绝。重启前的 ready 屏障让 apexd 在启动时知道哪些 session 应进入 activation。

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

java
void markStagedSessionReady(int sessionId)
        throws PackageManagerException {
    try {
        waitForApexService()
                .markStagedSessionReady(sessionId);
    } catch (RemoteException re) {
        throw new PackageManagerException(
                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                "Failed to mark apexd session as ready : "
                        + re.getMessage());
    } catch (Exception e) {
        throw new PackageManagerException(
                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                "Failed to mark apexd session as ready : "
                        + e.getMessage());
    }
}

Framework 把 apexd ready 失败统一映射为 verification failure;调用方应把 session 保持在失败/未 ready 状态,而不是继续假设重启后会激活。

7. 激活与确认 ​

apexd 在重启阶段把 STAGED session 变为 ACTIVATED,挂载新的 payload。系统完成启动并确认模块可用后,framework 调用 markStagedSessionSuccessful。

源码文件:system/apex/apexd/apexd.cpp,符号:MarkStagedSessionSuccessful

cpp
Result<void> MarkStagedSessionSuccessful(
    const int session_id) {
  auto session = gSessionManager->GetSession(session_id);
  if (!session.ok()) return session.error();
  if (session->GetState() == SessionState::SUCCESS) {
    return {};
  } else if (session->GetState()
             == SessionState::ACTIVATED) {
    OR_RETURN(DeleteBackup());
    return session->UpdateStateAndCommit(
        SessionState::SUCCESS);
  }
  return Error() << "Session " << session_id
                 << " can not be marked successful";
}

成功确认会删除 active APEX 备份并持久化 SUCCESS。如果 activation 未完成,调用 successful 会失败;如果标记 successful 的 Binder 调用失败,Framework 记录错误,但 apexd 仍可在后续状态恢复中处理。

8. PMS 扫描回接 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:APEX scan path

java
final boolean isApex =
        (scanFlags & SCAN_AS_APEX) != 0;
final File scanFile = new File(
        isApex ? request.getApexInfo().modulePath
                : request.getCodePath());

if (!isApex) {
    setUpCodePath(parsedPackage,
            pkgSetting, scanFlags);
}

APEX 扫描使用 apexd 返回的 modulePath,不使用原始 session 文件作为运行时 code path;普通 APK 才走 code path 设置、native 库提取等分支。这样 PMS 的 package model 与实际 active mount 对齐。

9. Non-staged 分支 ​

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

java
ApexInfo installPackage(File apexFile,
        boolean force) throws PackageManagerException {
    try {
        return waitForApexService()
                .installAndActivatePackage(
                        apexFile.getAbsolutePath(), force);
    } catch (RemoteException e) {
        throw new PackageManagerException(
                PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                "apexservice not available");
    } catch (Exception e) {
        throw new PackageManagerException(
                PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                e.getMessage());
    }
}

non-staged 路径直接让 apexd install-and-activate,成功后返回 ApexInfo,PMS 再以 modulePath 扫描注册。它的失败被映射为 internal error;它不经过 staged session 的 VERIFIED → STAGED → reboot 状态链。

10. 失败定位 ​

现象检查点
session 提交失败validateApexInstallLocked、apexd VerifyPackagesStagedInstall
提交成功但未激活是否调用 markStagedSessionReady,session 是否为 STAGED
重启后 APEX 未出现apexd activation 状态、PMS active scan/modulePath
APEX 扫描失败SCAN_AS_APEX、返回的 ApexInfo.modulePath
non-staged 失败installAndActivatePackage RemoteException/版本策略
回滚备份异常BackupActiveApexes、rollback session 状态

11. 阅读检查 ​

复述 staged 路径:PackageInstallerSession.validateApexInstallLocked → StagingManager.commitSession → ApexManager.submitStagedSession → apexd VERIFIED → markStagedSessionReady → 重启激活 → PMS 用 modulePath 扫描 → markStagedSessionSuccessful。

然后回答:为什么 APEX session 只能有一个文件?为什么 VERIFIED 还不能在重启激活?为什么 PMS 扫描不用原始 .apex 路径?答案分别对应 session 一致性、ready 屏障和 active mount 与包模型的一致性。