归档应用启动恢复
本文承接 PackageArchiver 元数据链 和 应用归档与解归档。上一篇讲元数据从哪里来,本文只讲用户点击归档应用之后发生什么:ActivityStartInterceptor 为什么返回 aborted,PackageArchiver 如何创建或复用 unarchive session,installer 如何回报空间不足或用户操作,成功后原始 Intent 又如何恢复执行。
1. 启动入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageArchiver.java、frameworks/base/services/core/java/com/android/server/wm/ActivityStartInterceptor.java。
启动一个 archived package 时,ActivityStartInterceptor 通过 PackageManagerInternal 查询归档状态;如果命中,就交给 PackageArchiver 请求恢复,而不是继续启动一个没有 APK 的 Activity。方法返回 START_ABORTED,因为当前 activity 并不存在,恢复完成后由保存的 IntentSender 再次启动。
if ((restrictions & RESTRICTION_CONFIRM_WITH_SPEEDBUMP) != 0) {
// distraction speedbump 是另一条限制;archive 启动会在
// PackageArchiver.requestUnarchiveOnActivityStart() 中处理。
}归档恢复的 caller 资格包括 default launcher、能解析 home intent 的 launcher app 和 shell 测试身份;普通应用不能借 Activity 启动路径替任意包触发 unarchive。
2. caller 资格
requestUnarchiveOnActivityStart 首先从 intent 得到目标 package 和 caller package,再通过 isCallerQualifiedForUnarchival 检查 default launcher、launcher activity 或 shell UID。若不满足,返回 START_PERMISSION_DENIED。
如果已有 active unarchive session 且 caller 具有 unarchival confirmation AppOp,PackageArchiver 会打开 PackageInstaller session details,返回 START_ABORTED,避免为同一包创建第二个恢复任务。
3. Draft Session
源码符号:createDraftSession。
PackageArchiver 为一次恢复创建 PackageInstaller draft session:
SessionParams params = new SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
params.setAppLabel(
mContext.getString(R.string.unarchival_session_app_label));
params.installFlags = INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE;session icon 来自 archived app icon;session owner 是 responsible installer。相同 user/package 的已有 draft session 会被复用,并把新的 listener 挂接到旧 session,避免重复恢复和重复下载。
draft session 还有 cleanup timeout。若 installer 在前台 allowlist 窗口内没有认领 session,PackageArchiver 会清理未认领 draft;设备重启清理仍是源码中的 TODO 边界。
4. Installer 请求
创建 session 后,PackageArchiver 在 handler 上调用 unarchiveInternal,发送有序广播:
Intent intent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
intent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, unarchiveId);
intent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME,
packageName);
intent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS,
userId == UserHandle.USER_ALL);
intent.setPackage(installerPackage);广播明确投递到 responsible installer,不是 launcher。FLAG_RECEIVER_FOREGROUND 和 temporary app allowlist 让 installer 可以在前台有限时间内执行网络/存储恢复;all-users 请求使用当前 user 作为实际 unarchive 操作 user。
5. 状态回报
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java。
installer 使用 reportUnarchivalStatus 回报恢复进度/结果。PMS 验证 unarchiveId、user、session owner UID,再把状态写入 PackageInstallerSession。
关键输入约束包括:
UNARCHIVAL_ERROR_USER_ACTION_NEEDED必须提供PendingIntent;UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE必须提供正的 requiredStorageBytes;- 其他状态不能携带 requiredStorageBytes;
- 调用者 UID 必须拥有对应 session。
这使“空间不足”和“需要确认”成为结构化失败,而不是普通字符串消息。
6. Listener 回调
PackageArchiver 为 launcher caller 缓存 (userId, packageName) -> IntentSender。内部 UnarchiveIntentSender 接收 installer/session 回报:成功状态直接结束;非成功状态若带用户操作 Intent 且 caller app 仍在 top visible,就以 FLAG_ACTIVITY_NEW_TASK 启动该 Intent。
int status = intent.getExtras().getInt(
PackageInstaller.EXTRA_UNARCHIVE_STATUS,
STATUS_PENDING_USER_ACTION);
if (status == UNARCHIVAL_OK) {
return;
}
Intent extraIntent = intent.getParcelableExtra(
Intent.EXTRA_INTENT, Intent.class);
UserHandle user = intent.getParcelableExtra(
Intent.EXTRA_USER, UserHandle.class);
if (extraIntent != null && user != null
&& mAppStateHelper.isAppTopVisible(mCallerPackageName)) {
extraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(extraIntent, user);
}top-visible 检查防止后台 launcher/caller 被恢复流程强行拉到前台。成功后原始 activity 的重新启动由后续 listener/status 语义完成,而不是在 PackageArchiver 中直接调用原始 Intent。
7. 用户确认
没有 INSTALL_PACKAGES 或 REQUEST_INSTALL_PACKAGES 的 caller,不能静默发起 unarchive;PackageArchiver 会发送 ACTION_UNARCHIVE_DIALOG,extras 携带 status receiver 和 package name。default launcher 在拥有 OP_UNARCHIVAL_CONFIRMATION 时可跳过确认;非 default launcher 默认要求确认。
这是两层校验:caller 是否有请求权限,以及本次是否必须显示确认。installer 的 capability 还会在 session 提交阶段再次校验,不能用 caller 权限替代 responsible installer 能力。
8. 完成恢复
installer 在获得资源后负责创建/提交真正的 unarchive install;PackageInstallerSession 使用 INSTALL_UNARCHIVE,InstallPackageHelper 进入没有 APK 的 PackageLite/ArchivedPackageParcel 解析分支。恢复成功后才重建 AndroidPackage、code path 和 user installed 状态。
在恢复完成前,ActivityStartInterceptor 每次仍会把启动视为 archived;已有 session 会被复用并打开 session details,而不是启动半恢复的包。
9. 失败定位
| 现象 | 检查点 | 解释 |
|---|---|---|
| 点击归档应用直接失败 | caller qualification、archive state | 非 launcher/shell 或包已不再归档 |
| 每次点击都新建 session | draft session key、cleanup timeout | 复用依据是 user + package + installer owner |
| installer 报空间不足但无 UI | requiredStorageBytes、status receiver | 空间不足状态必须携带正字节数 |
| 用户确认页不出现 | AppOp、INSTALL_PACKAGES、default launcher | 可能被静默能力跳过,或 PendingIntent 无效 |
| 恢复后原 Activity 未打开 | listener、top-visible、status | 成功不会在后台强制拉起 caller |
| session invalid | installer unarchival capability | 协议能力检查失败,不是普通 APK 签名失败 |
10. 源码练习
- 从
requestUnarchiveOnActivityStart追踪 caller qualification、draft session、ordered broadcast 和START_ABORTED的原因。 - 构造 installer 回报 insufficient storage,指出
requiredStorageBytes校验和 listener 向 launcher 传递用户操作的路径。 - 对比 default launcher 与普通 launcher app 的确认策略,标出 AppOps 和
INSTALL_PACKAGES两层边界。 - 追踪 unarchive session 从
reportUnarchivalStatus(OK)到 InstallPackageHelper 的parsePackageFromPackageLite,说明何时 archived 状态才真正结束。
