Skip to content

ApexManager 管理

追踪 Android 17 ApexManager 与 apexd 的 Binder 交互、扫描缓存、APK-in-APEX 映射和 staged session 错误语义。

基于android-17.0.0_r1
AndroidPMSAPEXapexd

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

java
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

java
@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

java
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

java
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

java
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

java
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

java
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

java
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

java
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

java
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 启动没有 APEXgetAllPackages、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 下”。