卸载回调
本文承接 卸载全流程、DeletePackageHelper 入口 和 数据清理流程。主题不是重复删除动作,而是回答“调用方什么时候知道卸载结果”:IPackageDeleteObserver2 如何区分需要用户操作和最终结果,PackageRemovedInfo 如何喂给广播,异步 Handler 为什么可能让回调晚于状态变化,以及部分用户删除时错误码如何表达。源码基线为 Android 17 android-17.0.0_r1。
1. 回调契约
1.1 AIDL 接口
源码文件:frameworks/base/core/java/android/content/pm/IPackageDeleteObserver2.aidl
/** @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
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
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_SUCCEEDED | deletePackageX 返回 true | 是 |
DELETE_FAILED_INTERNAL_ERROR | 包不存在、动作失败、App Lock | 可能未进入 |
DELETE_FAILED_USER_RESTRICTED | 系统更新非管理员删除 | 否 |
DELETE_FAILED_OWNER_BLOCKED | 全用户删除存在 block user | 部分用户可能已删除 |
DELETE_FAILED_APP_PINNED | locked task/pinned app | 否 |
DELETE_FAILED_FOR_CHILD_PROFILE | child profile 级联删除失败 | 父用户已成功,child 失败 |
4. 删除信息
4.1 填充时机
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.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
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 分支。
adb shell dumpsys package <package.name>
adb shell pm path <package.name>8. 源码路线
IPackageDeleteObserver2.aidl:明确用户操作和终态回调契约。DeletePackageHelper.deletePackageVersionedInternal:追踪确认、权限和 Handler 投递。deletePackageX:理解删除结果和PackageRemovedInfo的生成。BroadcastHelper.sendPackageRemovedBroadcasts:查看广播接收者和 extras。RemovePackageHelper:把状态清理、资源清理与回调时机对照起来。- child profile、block uninstall、系统包和 archive 分支:检查结果码是否表达部分完成。
卸载回调的核心是时序而不是接口名:确认回调表示“还没删”,删除回调表示“删除动作已经返回”,广播表示“系统组件可以开始响应状态变化”,而资源清理和接收端投递还可能各自延后。沿着 observer、PackageRemovedInfo、BroadcastHelper 和 Handler 四个对象阅读,才能判断一个“没收到卸载回调”的问题究竟是未获确认、删除失败、包可见性过滤,还是客户端 Binder 已经消失。
