Skip to content

归档应用启动恢复

追踪 Android 17 归档应用从启动拦截、draft session 到 installer 状态回报和恢复启动的异步协议。

基于android-17.0.0_r1
AndroidPMSPackageArchiverPackageInstallerUnarchive

归档应用启动恢复 ​

本文承接 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 再次启动。

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

java
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,发送有序广播:

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

java
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 或包已不再归档
每次点击都新建 sessiondraft session key、cleanup timeout复用依据是 user + package + installer owner
installer 报空间不足但无 UIrequiredStorageBytes、status receiver空间不足状态必须携带正字节数
用户确认页不出现AppOp、INSTALL_PACKAGES、default launcher可能被静默能力跳过,或 PendingIntent 无效
恢复后原 Activity 未打开listener、top-visible、status成功不会在后台强制拉起 caller
session invalidinstaller unarchival capability协议能力检查失败,不是普通 APK 签名失败

10. 源码练习 ​

  1. 从 requestUnarchiveOnActivityStart 追踪 caller qualification、draft session、ordered broadcast 和 START_ABORTED 的原因。
  2. 构造 installer 回报 insufficient storage,指出 requiredStorageBytes 校验和 listener 向 launcher 传递用户操作的路径。
  3. 对比 default launcher 与普通 launcher app 的确认策略,标出 AppOps 和 INSTALL_PACKAGES 两层边界。
  4. 追踪 unarchive session 从 reportUnarchivalStatus(OK) 到 InstallPackageHelper 的 parsePackageFromPackageLite,说明何时 archived 状态才真正结束。