DeletePackageHelper 入口
本文承接 卸载全流程。上一文从用户命令一路讲到系统包恢复和广播;本文把镜头收窄到 DeletePackageHelper,回答三个源码阅读问题:入口怎样把外部参数变成内部删除请求,DeletePackageAction 为什么要在锁内创建,以及单用户、全用户、静默卸载和 profile 级联如何改变控制流。读完后,读者可以从 deletePackageVersionedInternal 继续走到 deletePackageX、deletePackageLIF 和 executeDeletePackageLIF,并知道每一步由谁拥有状态、何时异步、失败后谁负责通知。
1. 类边界
1.1 依赖注入
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
final class DeletePackageHelper {
private final PackageManagerService mPm;
private final UserManagerInternal mUserManagerInternal;
private final RemovePackageHelper mRemovePackageHelper;
private final BroadcastHelper mBroadcastHelper;
DeletePackageHelper(PackageManagerService pm,
RemovePackageHelper removePackageHelper,
BroadcastHelper broadcastHelper) {
mPm = pm;
mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
mRemovePackageHelper = removePackageHelper;
mBroadcastHelper = broadcastHelper;
}
}该类是 package-private,职责是编排删除而不是独自操作所有资源。mPm 提供 mLock、mInstallLock、Settings 和快照;RemovePackageHelper 负责从运行时结构和文件系统移除;BroadcastHelper 负责把 PackageRemovedInfo 投影给其他进程;用户列表由 UserManagerInternal 提供。构造函数中的注释明确保留 PMS 依赖是待拆分事项,不能把它理解为完全独立的服务。
1.2 方法分层
| 层次 | 方法 | 主要 owner | 生效时机 |
|---|---|---|---|
| 入口 | deletePackageVersionedInternal | Binder caller + PMS snapshot | 同步校验后异步投递 |
| 编排 | deletePackageX | DeletePackageHelper | 拿锁、冻结、调用 LIF |
| 决策 | mayDeletePackageLocked | Settings/PackageSetting | mLock 内构造 action |
| 执行 | deletePackageLIF | DeletePackageHelper | mInstallLock 内改包状态 |
| 资源 | executeDeletePackageLIF、RemovePackageHelper | 文件/数据 owner | 状态修改后清理 |
| 通知 | BroadcastHelper、observer | 外部消费者 | 删除成功或失败后 |
2. 入口校验
2.1 参数与权限
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
public void deletePackageVersionedInternal(VersionedPackage versionedPackage,
IPackageDeleteObserver2 observer, int userId, int deleteFlags,
int callingUid, boolean allowSilentUninstall) {
mPm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
final Computer snapshot = mPm.snapshotComputer();
final boolean canViewInstantApps =
snapshot.canViewInstantApps(callingUid, userId);
Preconditions.checkNotNull(versionedPackage);
Preconditions.checkNotNull(observer);
Preconditions.checkArgumentInRange(
versionedPackage.getLongVersionCode(),
PackageManager.VERSION_CODE_HIGHEST, Long.MAX_VALUE,
"versionCode must be >= -1");
final String packageName = versionedPackage.getPackageName();
final long versionCode = versionedPackage.getLongVersionCode();
final String internalPackageName = snapshot.resolveInternalPackageName(
packageName, versionCode);
}入口先验证 DELETE_PACKAGES,再使用 snapshot 读取 instant-app 可见性和重命名后的内部包名。VersionedPackage 的 -1 表示不限定版本;其他版本号用于防止删除竞态中的错误版本。此处还没有改变 Settings,所以前置失败不会触发删除广播。
2.2 用户范围
final boolean deleteAllUsers =
(deleteFlags & PackageManager.DELETE_ALL_USERS) != 0;
final int[] users = deleteAllUsers
? mUserManagerInternal.getUserIds()
: new int[] { userId };
if (UserHandle.getUserId(callingUid) != userId
|| (deleteAllUsers && users.length > 1)) {
mPm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"deletePackage for user " + userId);
}DELETE_ALL_USERS 只改变目标用户数组,并不直接决定是否删除 APK;后续还要看其他用户是否安装、卸载阻止和系统包类型。跨用户调用需要额外权限,避免普通应用借助 userId 修改其他用户状态。
3. 用户确认
3.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 请求用户操作并立即返回;真正删除要等卸载确认 Activity 再次调用。Handler.post 使 Binder 回调异步执行,避免在入口线程直接运行外部代码。orphaned 包、系统/root、安装器、验证器和具备设备管理权限的调用方可能绕过该确认。
3.2 调用方判断
private boolean isCallerAllowedToSilentlyUninstall(
Computer snapshot, int callingUid, String pkgName, int[] targetUserIds) {
if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
return true;
}
final int callingUserId = UserHandle.getUserId(callingUid);
for (int user : targetUserIds) {
if (callingUid == snapshot.getPackageUid(
snapshot.getInstallerPackageName(pkgName, user),
0, callingUserId)) {
return true;
}
}
return snapshot.checkUidPermission(
Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS,
callingUid) == PackageManager.PERMISSION_GRANTED;
}安装该包的 installer、root/shell/system 和设备管理调用方拥有不同的静默权限来源。这里比较的是调用 UID 与 installer 包 UID,不是简单比较包名字符串;因此 installer 被替换或跨用户时,必须重新按 snapshot 判断。
4. 异步删除
4.1 投递工作
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
final long token = Binder.clearCallingIdentity();
try {
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 {
// 对未阻止用户逐一删除,最终仍报告 owner blocked
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);
});
} finally {
Binder.restoreCallingIdentity(token);
}清除 Binder calling identity 后,删除工作进入 PMS Handler。全用户删除遇到 blockUninstall 时不会简单地全部失败,而是对未阻止用户继续执行,再以 DELETE_FAILED_OWNER_BLOCKED 告知调用方存在阻止用户。这是“状态可能部分改变、结果仍反映阻止”的重要边界。
4.2 profile 级联
单用户删除成功后,源码会检查 child profiles;只有 profile 的 deleteAppWithParent 属性为 true 且包在 child 中已安装,才递归调用 deletePackageX。child 删除失败会把结果改为 DELETE_FAILED_FOR_CHILD_PROFILE。因此“用户 0 删除成功”不等于所有关联 profile 都已完成。
5. 删除动作
5.1 deletePackageX
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
public int deletePackageX(String packageName, long versionCode,
int userId, int deleteFlags, boolean removedBySystem,
int callingUid) {
final PackageRemovedInfo info = new PackageRemovedInfo();
final int removeUser = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0
? UserHandle.USER_ALL : userId;
final PackageSetting uninstalledPs;
final PackageSetting disabledSystemPs;
final AndroidPackage pkg;
synchronized (mPm.mLock) {
uninstalledPs = mPm.mSettings.getPackageLPr(packageName);
if (uninstalledPs == null) {
return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
}
if (versionCode != PackageManager.VERSION_CODE_HIGHEST
&& uninstalledPs.getVersionCode() != versionCode) {
return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
}
disabledSystemPs =
mPm.mSettings.getDisabledSystemPkgLPr(packageName);
pkg = mPm.mPackages.get(packageName);
}
try (PackageManagerTracedLock installLock =
mPm.mInstallLock.acquireLock()) {
try (PackageFreezer freezer = mPm.freezePackageForDelete(
packageName, removeUser, deleteFlags,
"deletePackageX", ApplicationExitInfo.REASON_OTHER)) {
final boolean result = deletePackageLIF(packageName,
UserHandle.of(removeUser), true,
mUserManagerInternal.getUserIds(),
deleteFlags | PackageManager.DELETE_CHATTY,
info, true, callingUid);
if (!result) {
return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
}
}
}
return PackageManager.DELETE_SUCCEEDED;
}deletePackageX 在 mLock 下读取并固定删除对象,再在 mInstallLock 下冻结和执行。PackageRemovedInfo 从此成为广播、用户列表和旧版本信息的共享载体;PackageFreezer 保证执行期间目标进程不会继续使用即将变化的包状态。两个锁不能互换:状态快照需要 mLock,文件/安装操作需要 mInstallLock。
5.2 版本与共享库
在构造 action 前,源码还会检查静态 shared library、SDK library 的客户端依赖。如果其他包仍要求该库,删除会被拒绝;只有启用相应的 SDK library independence 且所有客户端声明 optional 时才可能放行。版本号校验和共享库检查都发生在真正移除 mPackages 之前。
6. LIF 分支
6.1 构造 action
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
@GuardedBy("mPm.mLock")
public static DeletePackageAction mayDeletePackageLocked(
PackageRemovedInfo outInfo, PackageSetting ps,
PackageSetting disabledPs, int flags,
UserHandle user, int callingUid) {
if (ps == null) {
return null;
}
if (PackageManagerServiceUtils.isSystemApp(ps)) {
final boolean deleteSystem =
(flags & PackageManager.DELETE_SYSTEM_APP) != 0;
final boolean deleteAllUsers =
user == null || user.getIdentifier() == USER_ALL;
if ((!deleteSystem || deleteAllUsers) && disabledPs == null) {
return null;
}
}
return new DeletePackageAction(ps, disabledPs,
outInfo, flags, user, callingUid);
}DeletePackageAction 把删除前的 PackageSetting、disabled system setting、flags、用户和 caller UID 封装成不可重新猜测的上下文。未知系统包没有 disabled 记录时返回 null,防止调用者误删只读预装包;系统更新删除必须带内部 DELETE_SYSTEM_APP 语义并拥有对应用户范围。
6.2 执行分支
@GuardedBy("mPm.mInstallLock")
private void executeDeletePackageLIF(DeletePackageAction action,
String packageName, boolean deleteCodeAndResources,
int[] allUserHandles, boolean writeSettings,
boolean keepArtProfile) throws SystemDeleteException {
final PackageSetting ps = action.mDeletingPs;
final UserHandle user = action.mUser;
final boolean systemApp = PackageManagerServiceUtils.isSystemApp(ps);
final int userId = user == null ? USER_ALL : user.getIdentifier();
if ((!systemApp || (action.mFlags & PackageManager.DELETE_SYSTEM_APP) != 0)
&& userId != USER_ALL) {
markPackageUninstalledForUserLPw(ps, user, action.mFlags,
action.mCallingUid);
mRemovePackageHelper.clearPackageStateForUserLIF(
ps, userId, action.mFlags);
return;
}
if (systemApp) {
deleteInstalledSystemPackage(action, allUserHandles, writeSettings);
mPm.restoreDisabledSystemPackageLIF(
action, allUserHandles, writeSettings);
} else {
deleteInstalledPackageLIF(action, allUserHandles,
deleteCodeAndResources, action.mFlags,
action.mRemovedInfo, writeSettings);
}
}LIF 方法在 mInstallLock 下运行。单用户请求通常只改 per-user installed/enabled 状态并清理该用户数据;全量第三方删除进入 deleteInstalledPackageLIF;更新系统包进入删除 data 更新并恢复只读分区 APK 的专用路径。DeletePackageHelper 决定分支,RemovePackageHelper 执行底层资源处理。
7. 收尾与通知
7.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 状态被改写前采集。mRemovedUsers 决定哪些用户收到广播,mDataRemoved 影响 UID removed 语义,版本号用于诊断;它们不是删除完成后的再次查询结果。
7.2 广播与清理
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);
}
if (info.mArgs != null) {
mRemovePackageHelper.cleanUpResources(
info.mArgs.getPackageName(), info.mArgs.getCodeFile());
}广播消费者先看到 removed 事件,旧 code path 的最终资源清理随后执行。这给其他进程留出依据广播清理缓存的时间,也解释了为什么“收到广播”不等于所有文件已经同步消失。observer 结果和广播都消费 PackageRemovedInfo,但失败的前置检查不会构造成功通知。
8. 验证方法
8.1 用户确认与静默
普通应用调用删除 API 时,先观察是否收到 onUserActionRequired,确认后再观察 onPackageDeleted;使用 root、shell 或合法 installer 调用时,预期直接进入 deletePackageX。该实验验证入口权限与异步确认,不证明底层数据已清理。
8.2 阻止卸载
在多用户设备对一个用户设置 block uninstall,再执行 DELETE_ALL_USERS。预期未阻止用户可能被删除,但最终结果为 DELETE_FAILED_OWNER_BLOCKED。用 dumpsys package 检查各用户 installed 状态,可看到“结果失败”和“部分状态已改变”同时成立。
8.3 系统更新
更新系统应用后执行管理员允许的卸载更新,检查 deleteInstalledSystemPackage 与 restoreDisabledSystemPackageLIF 路径:当前 code path 回到只读分区,disabled 状态清除,用户状态按原集合恢复。若收到广播但 data 目录暂时存在,应继续检查延迟资源清理,而不是立即判定恢复失败。
9. 源码路线
PackageManagerService.deletePackageVersioned:确认 Binder 门面。deletePackageVersionedInternal:阅读权限、包名、用户和静默分支。isCallerAllowedToSilentlyUninstall:理解 installer、system 和设备管理权限。deletePackageX:跟踪 snapshot、锁、freezer 和结果对象。mayDeletePackageLocked:理解系统包与DeletePackageAction边界。deletePackageLIF/executeDeletePackageLIF:跟踪单用户、全量和系统包分支。PackageRemovedInfo、BroadcastHelper、RemovePackageHelper:确认通知和资源清理顺序。
DeletePackageHelper 的核心价值是把“能不能删”“删哪个用户”“删哪一种包”“何时通知”拆成可追踪的阶段。入口负责把外部请求归一化,action 固定删除上下文,LIF 方法在锁和 freezer 保护下改变状态,RemovePackageHelper 与 BroadcastHelper 分别完成资源和通知;这条分层主线比记住一个“卸载函数”更适合继续阅读源码和定位问题。
