Skip to content

应用归档与解归档

追踪 Android 17 应用归档的状态拆分、元数据保留、解归档安装和失败边界。

基于android-17.0.0_r1
AndroidPMSArchiveStatePackageInstallerPackageUserState

应用归档与解归档 ​

本文面向已经读过 用户应用卸载、PackageSetting 数据结构 和 PackageInstaller 会话管理 的读者。本文只讲 Android 17 的 archive/unarchive 机制:为什么归档后 APK 不在运行路径上,Launcher 却还能显示标题和图标;为什么它不是普通 DELETE_KEEP_DATA;解归档时 PackageInstaller 如何在没有 APK 的情况下重新建立安装请求。

1. 三层状态 ​

归档同时修改三层对象:

层Android 17 归档结果
PackageSettingpkg=null,保留包身份、签名、版本和 split fallback;pendingRestore=true
PackageUserState目标 user installed=false,保存 ArchiveState
代码/数据APK 代码移除;archive 路径清理 cache/code-cache,数据是否保留由删除 flags 决定

因此归档不是“把 installed 改 false”这么简单。全局包对象没有可执行 AndroidPackage,但 Settings 仍保留足够的信息完成查询、签名校验和后续解归档。

2. 归档状态 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:markPackageAsArchivedIfNeeded。

java
pkgSetting
        .setPkg(null)
        .setPendingRestore(true);
for (int userId : userIds) {
    pkgSetting.modifyUserState(userId).setInstalled(false);
}

for (int userId : userIds) {
    var archiveState = mInstallerService.mPackageArchiver
            .createArchiveState(archivePackage, userId,
                    responsibleInstallerPackage,
                    responsibleInstallerTitles.get(userId));
    if (archiveState != null) {
        pkgSetting.modifyUserState(userId)
                .setArchiveState(archiveState);
    }
}

归档前 ArchivedPackageParcel 必须包含 archived activities 和 responsible installer 信息。若 installer 为空,PMS 记录错误并不会创建完整 ArchiveState。每个 user 都有自己的 archive metadata,因此 user 0 归档并不自动意味着所有 user 的 UI 元数据完全相同。

3. 元数据内容 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/Settings.java;符号:writeArchiveStateLPr、parseArchiveState。

xml
<archive-state installer-title="Store" archive-time="..."><!-- hex --
    <archive-activity-info
        activity-title="Example"
        original-component-name="com.example/.MainActivity"
        icon-path="..."
        monochrome-icon-path="..." />
</archive-state>

ArchiveState 保存 installer title、archive time、每个主 Activity 的标题、原始 ComponentName 和 bitmap 路径。读取时 installer title 为空或 activity 列表为空会返回 null;这类损坏元数据不能被当作有效归档状态。

4. 归档查询 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:getArchivedPackageInternal。

java
ArchiveState archiveState = psi.getArchiveState();
if (archiveState == null && !psi.isInstalled()) {
    return null;
}
archPkg.signingDetails = ps.getSigningDetails();
archPkg.versionCode = (int) ps.getVersionCode();

查询要求跨用户权限和 package visibility。archiveState != null 时直接用保存的 Activity metadata 创建 ArchivedPackageParcel;否则对某些保留但未安装记录,PMS 会从当前 launcher activity infos 合成 metadata。

这解释了两个 flags 的区别:普通查询以 installed 为主;带 MATCH_ARCHIVED_PACKAGES 的路径可以看到 archived record,且返回的是 metadata,不代表 APK 代码已经可执行。

5. 解归档入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java、frameworks/base/core/java/android/content/pm/PackageManager.java。

Android 17 用 INSTALL_ARCHIVED 表示归档安装输入,用 INSTALL_UNARCHIVE 表示从现有 archive 恢复。Session 在提交前识别 INSTALL_UNARCHIVE,并在需要时避免重复的用户确认;installer 还必须支持 unarchival。

java
if (isArchivedInstallation()) {
    if (!isArchivedInstallationAllowed(existingPkgSetting)) {
        throw new PackageManagerException(
                INSTALL_FAILED_SESSION_INVALID,
                "Archived installation of this package is not allowed.");
    }
    if (!mPm.mInstallerService.mPackageArchiver
            .verifySupportsUnarchival(
                    getInstallSource().mInstallerPackageName, userId)) {
        throw new PackageManagerException(
                INSTALL_FAILED_SESSION_INVALID,
                "Installer has to support unarchival ...");
    }
}

当前 isArchivedInstallationAllowed 对已有 package state 的检查仍是严格边界;不能把任意普通 APK 安装请求伪装成 archived install。

6. 无 APK 解析 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java。

普通安装从 staged APK 解析 ParsedPackage;归档安装没有 APK,因此从 PackageLite 携带的 ArchivedPackageParcel 构造解析结果:

java
if (request.getPackageLite() == null || !request.isArchived()) {
    parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
    archivedPackage = null;
} else {
    // Archived install mode, no APK.
    parsedPackage = pp.parsePackageFromPackageLite(
            request.getPackageLite(), parseFlags);
    archivedPackage = request.getPackageLite().getArchivedPackage();
}

归档恢复仍需经过版本、签名、shared UID、ABI 和 package state reconciliation;“有 ArchiveState”不是跳过安装验证的通行证。它只是允许解析器使用保存的 package metadata 建立恢复请求。

7. 恢复完成 ​

解归档成功后,安装提交会重新建立 AndroidPackage、code path 和 per-user installed 状态,并清除 pending restore/archive state 的相应部分。具体清理由安装/提交路径完成,不能仅凭 INSTALL_UNARCHIVE flag 推断所有用户同时恢复。

一个 user 可以处于 unarchived,而另一个 user 仍保持 archived;读取 PackageUserState 时必须带目标 userId。全局 PackageSetting 是否重新拥有 pkg 与 user-level installed 是不同维度。

8. 数据与代码清理 ​

archive 介于完整卸载和 DELETE_KEEP_DATA 之间。DELETE_ARCHIVE 路径保留用户数据和 ArchiveState,但清理 cache/code-cache;ART profile 和代码 artifact 是否保留还受删除 flags 控制。归档的目标是释放 APK 代码空间,同时保留足够数据让未来恢复继续使用。

因此“归档成功但 data 目录仍在”是预期行为;“归档后 cache 仍在”则应检查 DeletePackageHelper 的 archive 清理分支,而不是直接把它当成失败。

9. 失败定位 ​

现象检查点解释
查询不到归档包user installed、ArchiveState、visibility、flags普通查询不会自动显示 archived record
Launcher 没有标题/图标ArchiveState activity info 与 icon path元数据缺失会让查询返回 null 或无法创建活动信息
解归档 session 无效INSTALL_UNARCHIVE、installer unarchival capabilityinstaller 不支持恢复会在提交前失败
归档恢复要求重新确认session user action 与 INSTALL_UNARCHIVE只有满足 installer/flag 条件才可跳过重复确认
恢复后仍未安装给某 userPackageUserState installedarchive/unarchive 是按 user 参与的状态转换
数据存在但 APK 不在pkg=null + pendingRestore这是归档保留状态,不代表扫描失败

10. 源码练习 ​

  1. 从 markPackageAsArchivedIfNeeded 追踪 pkg=null、pendingRestore、user installed 和 ArchiveState 的写入顺序。
  2. 对比普通 getPackageInfo 与 getArchivedPackage,说明为什么 ArchiveState 能提供 UI metadata 却不能提供可执行 AndroidPackage。
  3. 构造 installer 不支持 unarchival 的恢复请求,定位 INSTALL_FAILED_SESSION_INVALID 及其发生阶段。
  4. 给定 user 0 已解归档、user 10 仍归档的设备状态,列出查询和数据恢复时必须分别检查的 PackageSetting 与 PackageUserState 字段。