应用归档与解归档
本文面向已经读过 用户应用卸载、PackageSetting 数据结构 和 PackageInstaller 会话管理 的读者。本文只讲 Android 17 的 archive/unarchive 机制:为什么归档后 APK 不在运行路径上,Launcher 却还能显示标题和图标;为什么它不是普通 DELETE_KEEP_DATA;解归档时 PackageInstaller 如何在没有 APK 的情况下重新建立安装请求。
1. 三层状态
归档同时修改三层对象:
| 层 | Android 17 归档结果 |
|---|---|
PackageSetting | pkg=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。
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。
<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。
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。
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 构造解析结果:
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 capability | installer 不支持恢复会在提交前失败 |
| 归档恢复要求重新确认 | session user action 与 INSTALL_UNARCHIVE | 只有满足 installer/flag 条件才可跳过重复确认 |
| 恢复后仍未安装给某 user | PackageUserState installed | archive/unarchive 是按 user 参与的状态转换 |
| 数据存在但 APK 不在 | pkg=null + pendingRestore | 这是归档保留状态,不代表扫描失败 |
10. 源码练习
- 从
markPackageAsArchivedIfNeeded追踪pkg=null、pendingRestore、user installed 和 ArchiveState 的写入顺序。 - 对比普通
getPackageInfo与getArchivedPackage,说明为什么 ArchiveState 能提供 UI metadata 却不能提供可执行AndroidPackage。 - 构造 installer 不支持 unarchival 的恢复请求,定位
INSTALL_FAILED_SESSION_INVALID及其发生阶段。 - 给定 user 0 已解归档、user 10 仍归档的设备状态,列出查询和数据恢复时必须分别检查的 PackageSetting 与 PackageUserState 字段。
