PackageArchiver 元数据链
本文承接 应用归档与解归档,不再重复 ArchiveState 写入,而是深入 PackageArchiver:它怎样从 launcher activity 生成可持久化元数据,怎样把图标放入专用目录,怎样为不同 user 解析 responsible installer,以及 Launcher 点击归档应用后如何把请求交给 installer。本文不讨论 installer 下载 APK 的业务实现,只追踪 PMS/PackageInstaller 提供的接口契约。
1. 组件边界
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageArchiver.java。
PackageArchiver 是 PackageInstallerService 与 PMS package state 之间的桥。它不持有 APK 代码,而持有/生成 ArchiveState,并通过 ArchivedPackageParcel 在 Binder 边界传输标题、原始组件和图标字节。
2. 归档元数据来源
createArchiveStateInternal 从当前 package 的 launcher activities 生成 ArchiveActivityInfo:标题取 activity label,原始 component 保存为 ComponentName,图标则被转成 bitmap 文件引用。归档后即使 AndroidPackage 指针被清空,Launcher 仍可用这些数据画出应用入口。
for (LauncherActivityInfo info : mainActivities) {
ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
info.getLabel().toString(),
info.getComponentName(),
iconBitmapPath,
monochromeIconBitmapPath);
archiveActivityInfos.add(activityInfo);
}
return new ArchiveState(archiveActivityInfos, installerTitle);ArchiveState 的 activity 列表不能为空;Settings 读取时若 installer title 为空或没有 activity,会丢弃损坏状态。metadata 是恢复 UI 的最小资料集,不包含完整 manifest、权限或可执行代码。
3. 图标文件
源码符号:PackageArchiver 的 icon 存储与 getArchivedAppIcon。
归档时图标 bitmap 不直接塞入 XML,而是写入 system data 下的 package_archiver 目录,ArchiveState 只保存路径。查询 getArchivedAppIcon 时,PackageArchiver 从 archive activity info 选择主图标,读取并解码 bitmap;找不到文件时返回 null 或记录错误。
这让 package-restrictions 文件保持小型,也允许 Launcher 只在需要展示时读取图标。清理 archive state 时,PackageArchiver 还要删除对应 icon 文件;否则反复归档会积累无引用图片。
4. Installer 来源
归档不仅需要保存“谁安装了它”,还要为每个 user 解析 installer label。getResponsibleInstallerPackage 从 InstallSource 得到包名,再以目标 user 查询 ApplicationInfo 和 label;包在某 user 不可见或已经卸载时,可能无法生成 installer title,归档会记录错误并拒绝创建完整 state。
多用户场景中同一 package 可以在不同 user 使用不同 installer title。markPackageAsArchivedIfNeeded 传入 SparseArray<String> responsibleInstallerTitles,按 user 构造 ArchiveState,而不是复用一个全局标题。
5. Launcher 读取
PMS 的 getArchivedPackageInternal 先做 cross-user permission 和 package visibility 检查,再从 PackageUserState.getArchiveState() 构造 ArchivedPackageParcel。PackageArchiver.createArchivedActivities 将保存的 bitmap 文件重新编码为 parcel 字节。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:getArchivedPackageInternal。
ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
archPkg.packageName = packageName;
archPkg.signingDetails = ps.getSigningDetails();
archPkg.versionCode = (int) ps.getVersionCode();
archPkg.archivedActivities = PackageArchiver
.createArchivedActivities(archiveState);即便包没有 AndroidPackage,parcel 仍可以携带签名和版本信息,供 Launcher/installer 判断这是哪个历史包。若 ArchiveState 缺失且 user 未安装,PMS 返回 null,避免把普通卸载记录伪装成归档应用。
6. 点击后的请求
Launcher 请求 unarchive 时,PackageArchiver 验证包确实 archived、存在 responsible installer,并根据 caller 是否 launcher/系统组件决定是否需要用户确认。没有 installer 时返回错误并通知 status receiver;有 installer 时创建 unarchiveId,发送 ACTION_UNARCHIVE_PACKAGE,extras 携带 package、user、unarchive id 和是否所有用户恢复。
Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ID,
unarchiveId);
unarchiveIntent.putExtra(
PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
unarchiveIntent.putExtra(
PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS, unarchiveAllUsers);
unarchiveIntent.setPackage(installerPackage);Intent 指向 responsible installer,而不是任意调用者指定的包;这样恢复请求仍回到原安装来源的 unarchive 实现。
7. 前台启动保护
requestUnarchiveOnActivityStart 由 Activity 启动链调用。当 intent 目标属于 archived package 时,PackageArchiver 可以返回 IntentSender,把原始启动封装进 unarchive 请求。恢复动作使用 ActivityOptions 的 temporary app allowlist,在前台有限时间内允许 installer 工作,避免后台 installer 因后台执行限制无法下载/恢复包。
如果当前调用者已经是 installer 或 Wellbeing/launcher 等允许角色,部分确认步骤可以跳过;否则会启动确认流程。安全边界是“包已归档但仍可显示 UI”,而不是“任意 Activity 启动自动安装 APK”。
8. installer 能力
PackageArchiver 的 verifySupportsUnarchival 查询 responsible installer 是否声明 unarchive 能力。PackageInstallerSession 在 INSTALL_UNARCHIVE 提交阶段再次验证 installer capability;两处检查分别防止请求构造阶段和 session 提交阶段使用不支持恢复的 installer。
如果 installer 不支持,session 抛 INSTALL_FAILED_SESSION_INVALID。这不是 APK 下载失败,而是恢复协议能力不满足。
9. 状态与清理
archive state 清除时,PackageArchiver 删除 icon 文件并通过 PackageState mutation 将 user ArchiveState 设为 null。完整 unarchive 成功后,安装提交负责恢复 AndroidPackage、code path 和 installed;PackageArchiver 不直接承担代码安装。
归档失败时要区分三种状态:
- metadata 还没生成:package 可能仍是正常 installed;
ArchiveState已生成但 APK 删除失败:需要检查 DeletePackageHelper 清理阶段;- APK 已移除、state 已归档但 installer 恢复失败:包保持 archived,等待下次 unarchive,不应当被当成普通卸载。
10. 失败定位
| 现象 | 首先检查 | 解释 |
|---|---|---|
| Launcher 没有图标 | icon path、bitmap decode、archive activity info | XML 只存路径,文件可能缺失 |
| 无法请求恢复 | responsible installer、caller/user 权限 | PackageArchiver 找不到可用 installer |
| 恢复 session invalid | verifySupportsUnarchival | installer 协议能力缺失,不是 APK 解析错误 |
| 点击归档应用没有确认页 | caller 角色与 requestUnarchiveOnActivityStart | launcher/installer 场景可能跳过确认 |
| 恢复请求到错误 installer | InstallSource 与 setPackage | unarchive intent 指向 responsible installer |
| user 0 能显示、user 10 不显示 | per-user ArchiveState、installer title | 归档元数据按 user 保存 |
11. 源码练习
- 从
createArchiveStateInternal追踪 launcher activity label、ComponentName 和 icon path 如何进入 ArchiveState。 - 给定 icon 文件删除但 XML 仍存在,判断
getArchivedAppIcon、Launcher 显示和后续 unarchive 哪些步骤会受影响。 - 对比 launcher 点击归档应用与普通应用调用
requestUnarchive,说明 responsible installer、确认页和 temporary allowlist 的区别。 - 追踪
INSTALL_UNARCHIVE从 PackageArchiver Intent 到 PackageInstallerSession capability 校验,再到 InstallPackageHelper 的无 APK parse 分支。
