Skip to content

卸载回调

追踪卸载请求的用户确认、删除结果、广播和异步回调分发路径。

基于android-17.0.0_r1
AndroidPackageManagerService卸载回调源码阅读

卸载回调 ​

本文承接 卸载全流程、DeletePackageHelper 入口 和 数据清理流程。主题不是重复删除动作,而是回答“调用方什么时候知道卸载结果”:IPackageDeleteObserver2 如何区分需要用户操作和最终结果,PackageRemovedInfo 如何喂给广播,异步 Handler 为什么可能让回调晚于状态变化,以及部分用户删除时错误码如何表达。源码基线为 Android 17 android-17.0.0_r1。

1. 回调契约 ​

1.1 AIDL 接口 ​

源码文件:frameworks/base/core/java/android/content/pm/IPackageDeleteObserver2.aidl

java
/** @hide */
oneway interface IPackageDeleteObserver2 {
    void onUserActionRequired(in Intent intent);

    @UnsupportedAppUsage
    void onPackageDeleted(String packageName, int returnCode, String msg);
}

接口是 oneway,PMS 发起 Binder 调用后不会等待客户端方法执行。onUserActionRequired 是中间结果:删除尚未执行,调用方需要启动确认 UI;onPackageDeleted 是终态结果,携带包名、DELETE_* 错误码和诊断文本。两者不是互斥的错误回调,而是“确认阶段”和“删除阶段”的两个时点。

1.2 结果 owner ​

DeletePackageHelper 计算 returnCode,PackageRemovedInfo 保存成功删除的用户、UID、数据删除标记和广播 allowlist,BroadcastHelper 通知系统组件,observer 则通知原始调用方。广播收到不代表 observer 一定仍然存活;observer 断开也不会逆转已完成的删除。

2. 用户操作回调 ​

2.1 触发条件 ​

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

java
if (!isOrphaned(snapshot, internalPackageName)
        && !allowSilentUninstall
        && !isCallerAllowedToSilentlyUninstall(
                snapshot, callingUid, internalPackageName, users)) {
    mPm.mHandler.post(() -> {
        final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
        intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
        intent.putExtra(PackageInstaller.EXTRA_CALLBACK,
                new PackageManager.UninstallCompleteCallback(observer.asBinder()));
        observer.onUserActionRequired(intent);
    });
    return;
}

这一分支在删除前返回,observer 收到的是“请启动确认”的 Intent,而不是失败码。Intent 中的 UninstallCompleteCallback 保存原始 observer 的 Binder,确认 Activity 后续可以重新进入 PMS 完成真正删除。由于调用通过 mHandler.post 异步执行,入口线程不会直接运行客户端 Binder 代码。

2.2 回调失败 ​

如果 observer 进程已退出,RemoteException 被捕获并忽略;此时没有删除发生,因为流程在 return 前结束。调用方应把 onUserActionRequired 看作一次可重试的交互请求,而不是把“没有收到确认回调”解释成包已被删除。

3. 最终结果 ​

3.1 异步删除任务 ​

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

java
mPm.mHandler.post(() -> {
    final int returnCode;
    if (!deleteAllUsers) {
        returnCode = deletePackageX(internalPackageName, versionCode,
                userId, deleteFlags, false, callingUid);
    } else {
        final int[] blocked = getBlockUninstallForUsers(
                mPm.snapshotComputer(), internalPackageName, users);
        if (ArrayUtils.isEmpty(blocked)) {
            returnCode = deletePackageX(internalPackageName, versionCode,
                    userId, deleteFlags, false, callingUid);
        } else {
            for (int targetUserId : users) {
                if (!ArrayUtils.contains(blocked, targetUserId)) {
                    deletePackageX(internalPackageName, versionCode,
                            targetUserId,
                            deleteFlags & ~PackageManager.DELETE_ALL_USERS,
                            false, callingUid);
                }
            }
            returnCode = PackageManager.DELETE_FAILED_OWNER_BLOCKED;
        }
    }
    observer.onPackageDeleted(packageName, returnCode, null);
});

真正的 observer 结果在 Handler 任务中发送。全用户删除遇到 block uninstall 时,未阻止用户可能已经完成删除,但最终 return code 仍为 DELETE_FAILED_OWNER_BLOCKED;这不是事务回滚,而是对调用方报告“存在被阻止用户”。

3.2 结果码来源 ​

错误码典型来源是否进入删除执行
DELETE_SUCCEEDEDdeletePackageX 返回 true是
DELETE_FAILED_INTERNAL_ERROR包不存在、动作失败、App Lock可能未进入
DELETE_FAILED_USER_RESTRICTED系统更新非管理员删除否
DELETE_FAILED_OWNER_BLOCKED全用户删除存在 block user部分用户可能已删除
DELETE_FAILED_APP_PINNEDlocked task/pinned app否
DELETE_FAILED_FOR_CHILD_PROFILEchild profile 级联删除失败父用户已成功,child 失败

4. 删除信息 ​

4.1 填充时机 ​

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

java
outInfo.mRemovedUsers = userId == USER_ALL
        ? ps.queryUsersInstalledOrHasData(allUserHandles)
        : new int[] { userId };
outInfo.populateBroadcastUsers(ps);
outInfo.mDataRemoved =
        (flags & PackageManager.DELETE_KEEP_DATA) == 0;
outInfo.mRemovedPackage = ps.getPackageName();
outInfo.mRemovedPackageVersionCode = ps.getVersionCode();

这些字段在修改 PackageSetting 前采集,保证广播能知道原始版本、受影响用户和数据是否删除。mBroadcastAllowList 还会依据包可见性规则限制接收者;observer 本身不读取该 allowlist,它只接收自己的 return code。

4.2 广播与 observer ​

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

java
if (res) {
    final boolean killApp =
            (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
    final boolean isArchived =
            (deleteFlags & PackageManager.DELETE_ARCHIVE) != 0;
    mBroadcastHelper.sendPackageRemovedBroadcasts(
            info, mPm, killApp, removedBySystem, isArchived);
    mBroadcastHelper.sendSystemPackageUpdatedBroadcasts(info);
}

// 广播发出后再清理旧资源
if (info.mArgs != null) {
    mRemovePackageHelper.cleanUpResources(
            info.mArgs.getPackageName(), info.mArgs.getCodeFile());
}

广播只在 res 为 true 时发送;前置失败不会伪造 removed 广播。资源清理在广播之后,observer 的发送则由入口 Handler 在 deletePackageX 返回后完成,所以三者的时间顺序不能简化为“删除完成后同时通知”。

5. Session 适配 ​

5.1 Session 对照 ​

卸载没有与安装 session 完全相同的 InstallResult 适配层;调用 PackageManager.deletePackageVersioned 的客户端直接实现 IPackageDeleteObserver2。如果调用方是系统卸载器,它会把 observer Binder 放进确认 Intent,确认后沿原 callback 回传。

5.2 广播字段 ​

BroadcastHelper.sendPackageRemovedBroadcasts 会依据 PackageRemovedInfo 计算 ACTION_PACKAGE_REMOVED、ACTION_PACKAGE_FULLY_REMOVED、UID removed 和系统包更新通知。DELETE_KEEP_DATA、archive、mRemovedForAllUsers 和 mIsRemovedPackageSystemUpdate 会影响 extras 与广播类型;这些广播面向系统组件,不替代 observer 的删除返回码。

6. 失败与断连 ​

6.1 前置失败 ​

权限不足、App Lock、pinned、系统更新管理员限制和 shared library 依赖失败时,observer 可能立即或经 Handler 收到错误码;因为 PackageRemovedInfo 尚未进入成功广播路径,客户端不应等待 ACTION_PACKAGE_REMOVED 来判断失败。

6.2 接收端消失 ​

observer 的 Binder 断开只影响结果投递,不影响已经执行的删除。广播接收者也可能因包可见性过滤而收不到事件,因此诊断应优先查看 dumpsys package 的状态和 PMS 日志,再判断回调是否到达。

7. 验证方法 ​

7.1 用户确认 ​

使用没有静默卸载资格的调用方删除包,断言先收到 onUserActionRequired,确认后才收到 onPackageDeleted(DELETE_SUCCEEDED, ...)。确认前 pm path 和包状态应保持不变;这证明回调是两阶段协议。

7.2 部分删除 ​

多用户设备中对一个用户设置 block uninstall,再请求 DELETE_ALL_USERS。记录 observer 返回 DELETE_FAILED_OWNER_BLOCKED,同时检查未阻止用户的 installed 状态可能已清除。该实验验证结果码不等于全局事务回滚。

7.3 广播顺序 ​

注册可见的 PACKAGE_REMOVED 接收者,同时监听 observer 和 code path 删除。预期先能收到广播,再看到旧资源最终清理;如果 observer 先到达,说明调用路径或测试对象不是普通 deletePackageX 分支。

bash
adb shell dumpsys package <package.name>
adb shell pm path <package.name>

8. 源码路线 ​

  1. IPackageDeleteObserver2.aidl:明确用户操作和终态回调契约。
  2. DeletePackageHelper.deletePackageVersionedInternal:追踪确认、权限和 Handler 投递。
  3. deletePackageX:理解删除结果和 PackageRemovedInfo 的生成。
  4. BroadcastHelper.sendPackageRemovedBroadcasts:查看广播接收者和 extras。
  5. RemovePackageHelper:把状态清理、资源清理与回调时机对照起来。
  6. child profile、block uninstall、系统包和 archive 分支:检查结果码是否表达部分完成。

卸载回调的核心是时序而不是接口名:确认回调表示“还没删”,删除回调表示“删除动作已经返回”,广播表示“系统组件可以开始响应状态变化”,而资源清理和接收端投递还可能各自延后。沿着 observer、PackageRemovedInfo、BroadcastHelper 和 Handler 四个对象阅读,才能判断一个“没收到卸载回调”的问题究竟是未获确认、删除失败、包可见性过滤,还是客户端 Binder 已经消失。