ApexManager 管理
本文承接 APEX 架构,专门讲 ApexManager 这个 framework 门面:它如何等待 apexservice,把 apexd 的 ApexInfo 变成 PMS 可用的缓存和映射,以及为什么不同 API 对 RemoteException 的处理不同。本文不重复 APEX 容器格式,也不展开 staged 安装会话的上层 PackageInstallerSession 流程。
ApexManager 的关键职责不是挂载 APEX。挂载和激活由 apexd 拥有;ApexManagerImpl 负责 Binder 调用、结果转换、扫描结果缓存、APK-in-APEX 注册和错误映射。
1. 门面与实现
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:singleton 与 ApexManagerImpl
private static final Singleton<ApexManager>
sApexManagerSingleton = new Singleton<>() {
@Override
protected ApexManager create() {
return new ApexManagerImpl();
}
};
public static ApexManager getInstance() {
return sApexManagerSingleton.get();
}
protected static class ApexManagerImpl
extends ApexManager {
private final Object mLock = new Object();
@GuardedBy("mLock")
private Set<ActiveApexInfo> mActiveApexInfosCache;
}PMS 通过抽象类获取单例;实现内部维护自己的 lock 和缓存。测试可以替换/构造 ApexManagerImpl,生产路径则通过 ServiceManager.waitForService("apexservice") 取得 Binder。
2. Binder 连接
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:waitForApexService、getAllApexInfos
@VisibleForTesting
protected IApexService waitForApexService() {
return IApexService.Stub.asInterface(
Binder.allowBlocking(
ServiceManager.waitForService(
"apexservice")));
}
@Override
ApexInfo[] getAllApexInfos() {
try {
return waitForApexService().getAllPackages();
} catch (RemoteException re) {
Slog.e(TAG,
"Unable to retrieve packages from apexservice",
re);
throw new RuntimeException(re);
}
}waitForService 会阻塞直到 apexd 服务可用;getAllPackages 的 RemoteException 被提升为 RuntimeException,因为 PMS 启动扫描没有可用的 APEX 列表就无法继续建立系统包模型。
3. 扫描结果缓存
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:notifyScanResultLocked
private void notifyScanResultLocked(
List<ScanResult> scanResults) {
mPackageNameToApexModuleName = new ArrayMap<>();
mApexModuleNameToActivePackageName = new ArrayMap<>();
for (ScanResult scanResult : scanResults) {
ApexInfo ai = scanResult.apexInfo;
String packageName = scanResult.packageName;
mPackageNameToApexModuleName.put(
packageName, ai.moduleName);
if (ai.isActive) {
if (mApexModuleNameToActivePackageName
.containsKey(ai.moduleName)) {
throw new IllegalStateException(
"Two active packages have the same APEX module name: "
+ ai.moduleName);
}
mApexModuleNameToActivePackageName.put(
ai.moduleName, packageName);
}
}
}PMS 扫描完成后把 package name → module name 和 active module name → package name 两张表交给 ApexManager 重建。重复 active module 是不变量破坏,会抛异常而不是选择一个“较新”的结果。
4. Active 信息缓存
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:getActiveApexInfos
public List<ActiveApexInfo> getActiveApexInfos() {
synchronized (mLock) {
if (mActiveApexInfosCache == null) {
mActiveApexInfosCache = new ArraySet<>();
try {
ApexInfo[] activePackages =
waitForApexService().getActivePackages();
for (ApexInfo apexInfo : activePackages) {
mActiveApexInfosCache.add(
new ActiveApexInfo(apexInfo));
}
} catch (RemoteException e) {
Slog.e(TAG,
"Unable to retrieve packages from apexservice",
e);
}
}
return mActiveApexInfosCache == null
? Collections.emptyList()
: new ArrayList<>(mActiveApexInfosCache);
}
}首次查询才访问 apexd,后续返回副本而不是内部集合。RemoteException 时返回空列表,和启动扫描的 fatal 语义不同:这是查询 API 的降级策略,调用者必须能处理“暂时没有 active 信息”。
5. 模块映射
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:查询映射 API
public String getApexModuleNameForPackageName(
String apexPackageName) {
synchronized (mLock) {
Preconditions.checkState(
mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
return mPackageNameToApexModuleName
.get(apexPackageName);
}
}
public String getActivePackageNameForApexModuleName(
String apexModuleName) {
synchronized (mLock) {
Preconditions.checkState(
mApexModuleNameToActivePackageName != null,
"APEX packages have not been scanned");
return mApexModuleNameToActivePackageName
.get(apexModuleName);
}
}APEX package name 与 payload module name 是两个命名空间。扫描前调用查询会抛 IllegalStateException,不是返回 null;扫描后找不到合法映射才返回 null。这个区别能帮助定位“初始化顺序问题”和“确实不存在模块”的差异。
6. APK-in-APEX
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:registerApkInApex、getApksInApex
void registerApkInApex(AndroidPackage pkg) {
synchronized (mLock) {
for (ActiveApexInfo aai : mActiveApexInfosCache) {
if (pkg.getBaseApkPath().startsWith(
aai.apexDirectory.getAbsolutePath()
+ File.separator)) {
List<String> apks = mApksInApex.get(
aai.apexModuleName);
if (apks == null) {
apks = Lists.newArrayList();
mApksInApex.put(aai.apexModuleName,
apks);
}
apks.add(pkg.getPackageName());
}
}
}
}APK-in-APEX 的归属由实际 base APK path 是否位于 active apex directory 判断,再按 module name 建立列表。它不是通过 package name 前缀猜测,也不是把内嵌 APK 当作普通 /data/app 包。
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:reportErrorWithApkInApex
void reportErrorWithApkInApex(
String scanDirPath, String errorMsg) {
synchronized (mLock) {
for (ActiveApexInfo aai : mActiveApexInfosCache) {
if (scanDirPath.startsWith(
aai.apexDirectory.getAbsolutePath())) {
mErrorWithApkInApex.put(
aai.apexModuleName, errorMsg);
}
}
}
}扫描 APK-in-APEX 失败时,ApexManager 按 module 保存错误;后续通过外层 APEX package name 查询错误。缓存的错误用于诊断,不会自动把 APEX 标成 active/inactive。
7. Staged session
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:submitStagedSession、markStagedSessionReady
ApexInfoList submitStagedSession(
ApexSessionParams params)
throws PackageManagerException {
try {
ApexInfoList result = new ApexInfoList();
waitForApexService().submitStagedSession(
params, result);
return result;
} catch (RemoteException re) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
"apexd verification failed : "
+ re.getMessage());
} catch (Exception e) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
"apexd verification failed : "
+ e.getMessage());
}
}
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");
}
}验证异常在 framework 层统一映射为 INSTALL_FAILED_VERIFICATION_FAILURE;ready 失败同样是安装验证失败。markStagedSessionSuccessful 则采用不同策略:激活后标记失败只记录日志,因为 apexd 可以自行恢复 session 状态。
8. 回滚数据接口
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:snapshotCeData、restoreCeData
public boolean snapshotCeData(int userId,
int rollbackId, String apexPackageName) {
String moduleName;
synchronized (mLock) {
Preconditions.checkState(
mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
moduleName = mPackageNameToApexModuleName
.get(apexPackageName);
}
if (moduleName == null) {
Slog.e(TAG, "Invalid apex package name: "
+ apexPackageName);
return false;
}
try {
waitForApexService().snapshotCeData(
userId, rollbackId, moduleName);
return true;
} catch (Exception e) {
Slog.e(TAG, e.getMessage(), e);
return false;
}
}回滚数据 API 接收的是 PMS 使用的 APEX package name,却向 apexd 转换为 module name。映射不存在返回 false;远程异常也返回 false,调用方必须把 false 作为回滚流程中的失败信号处理。
9. 系统服务与 dump
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:notifyScanResultLocked、getApexSystemServices
for (ParsedApexSystemService service
: scanResult.pkg.getApexSystemServices()) {
if (service.getMinSdkVersion() != null
&& !UnboundedSdkLevel.isAtLeast(
service.getMinSdkVersion())) {
continue;
}
if (ai.isActive) {
ApexSystemServiceInfo info =
new ApexSystemServiceInfo(
service.getName(),
service.getJarPath(),
service.getInitOrder());
mApexSystemServices.add(info);
}
}
Collections.sort(mApexSystemServices);只有 active APEX 中、SDK 条件匹配的系统服务才进入列表,并按 init order 排序。duplicate service name 会抛异常,避免 system_server 以不确定顺序加载两个同名服务。
dump() 还会把 apexd session 映射为 UNKNOWN、VERIFIED、STAGED、ACTIVATED、SUCCESS、REVERTED 等状态,适合排查“session 提交成功但未激活”的问题。
10. 失败定位
| 现象 | 检查点 |
|---|---|
| PMS 启动没有 APEX | getAllPackages、apexservice 是否可用 |
| active 查询为空 | active cache、apexd getActivePackages、异常降级 |
| module/package 映射为空 | 是否已 notifyScanResult,名称是否混用 |
| APK-in-APEX 查不到 | base APK path 是否在 active apex directory |
| staged 安装失败 | submit/ready 的 INSTALL_FAILED_VERIFICATION_FAILURE |
| 同名系统服务冲突 | notifyScanResultLocked duplicate 检查 |
| 回滚数据失败 | package→module 映射与 apexd snapshot API |
11. 阅读检查
沿源码复述:PMS 启动调用 ApexManager.getAllApexInfos → InstallPackageHelper.scanApexPackages → notifyScanResult 重建映射;staged 更新则经过 submitStagedSession → markStagedSessionReady → apexd 激活 → PMS 下次扫描。
然后回答:ApexManager 是否挂载 payload?为什么 active cache 返回副本?为什么 APK-in-APEX 必须按路径注册?答案分别是“否,由 apexd”“避免外部修改内部集合”“路径才证明它位于 active APEX mount 下”。
