APEX 安装更新
本文承接 APEX 架构 与 ApexManager 管理,专门解释一次 APEX 更新如何从 PackageInstallerSession 进入 apexd,再回到 PMS 的扫描/注册路径。重点是状态屏障和失败语义,不重复 APK 安装或 APEX 容器格式。
APEX 更新与普通 APK 更新的根本差异是生效时机:APK 可以在当前用户安装提交后生效;staged APEX 先由 apexd 验证并保存会话,只有 markStagedSessionReady 后才会在下一次启动进入激活流程。PMS 不直接挂载 payload,也不能把“session 提交成功”当成“模块已运行”。
1. 两种安装模式
| 模式 | apexd API | 生效时机 | 主要约束 |
|---|---|---|---|
| staged | submitStagedSession → markStagedSessionReady | 重启后 | 可包含多个 APEX,需会话状态持久化 |
| non-staged | installAndActivatePackage | 当前运行期 | 单 APEX、仅支持允许热更新的模块/调试场景 |
2. Session 类型判断
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java,符号:isApexSession
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
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 清单
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
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
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
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
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
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
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
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
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 与包模型的一致性。
