卸载全流程
本文面向已经读过 系统应用更新、安装回调 的读者。主题是一个卸载请求如何从 shell/Binder 进入 DeletePackageHelper,再根据用户范围、系统包状态、保留数据和策略限制选择不同动作。这里不展开数据目录清理的每个 installd 参数,那是 数据清理流程 的主题;本文只追踪卸载决策、状态变化、广播和恢复入口。
1. 入口分叉
1.1 Shell 命令
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
case "uninstall":
return runUninstall();runUninstall() 解析包名、版本号、用户和删除 flags,随后通过 IPackageManager.deletePackageVersioned 进入 system_server。shell 命令本身不决定“删除 APK 还是仅标记用户未安装”,这个判断由 DeletePackageHelper 结合现有 PackageSetting 完成。
1.2 Binder 入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void deletePackageVersioned(VersionedPackage versionedPackage,
IPackageDeleteObserver2 observer, int userId, int deleteFlags) {
mDeletePackageHelper.deletePackageVersionedInternal(
versionedPackage, observer, userId, deleteFlags,
/* allowSilentUninstall= */ false);
}PMS 是 Binder 门面,DeletePackageHelper 才是卸载决策 owner。VersionedPackage 的版本号用于防止并发场景误删;observer 是异步结果消费者。allowSilentUninstall=false 表示普通 API 不自动跳过用户确认策略。
2. 前置检查
2.1 权限与包名
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
mPm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
final Computer snapshot = mPm.snapshotComputer();
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);删除权限、非空参数和版本范围在进入异步删除前检查。resolveInternalPackageName 处理 renamed package 和静态库名称,后续 Settings 查询使用内部真实名称。
2.2 用户限制与应用锁
同一方法还检查 App Lock、locked task、静默卸载资格和 DELETE_ALL_USERS。例如应用被固定在 locked task 时,observer 会直接收到 DELETE_FAILED_APP_PINNED;启用 App Lock 时,非 system UID 不能删除受保护包。这些失败不会创建 DeletePackageAction,也不会改变包状态。
2.3 系统更新删除权限
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
if (PackageManagerServiceUtils.isUpdatedSystemApp(uninstalledPs)
&& ((deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0)) {
final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
if (userInfo == null || !userInfo.isAdmin()) {
return PackageManager.DELETE_FAILED_USER_RESTRICTED;
}
}删除系统应用更新意味着恢复预装版本,因此 Android 17 对非管理员用户设置额外限制。这里拒绝的是“卸载更新”动作,不是普通用户对自己的第三方 APK 卸载。
3. 删除范围
3.1 单用户卸载
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
final int removeUser = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0
? UserHandle.USER_ALL : userId;
...
if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0)
&& userId != UserHandle.USER_ALL) {
synchronized (mPm.mLock) {
markPackageUninstalledForUserLPw(ps, user, flags, callingUid);
if (!systemApp) {
if (ps.isInstalledOnAnyOtherUser(allUsers, userId)
|| mPm.shouldKeepUninstalledPackageLPr(packageName)) {
clearPackageStateAndReturn = true;
} else {
mPm.mSettings.writeKernelMappingLPr(ps);
clearPackageStateAndReturn = false;
}
} else {
clearPackageStateAndReturn = true;
}
}
}单用户卸载先修改该用户的 installed 状态。第三方包如果仍被其他用户安装,只清理目标用户数据并保留全局 APK;若没有其他用户使用,才继续全量删除。系统包的单用户卸载通常只清理该用户状态,原始系统代码仍保留。
3.2 全用户卸载
设置 DELETE_ALL_USERS 后,removeUser=USER_ALL,outInfo.mRemovedUsers 由所有已安装或有数据的用户计算。第三方包会进入真正的 code/data 删除;系统更新包则进入“删除 data 更新、恢复 system APK”的专用路径。
4. 冻结与动作
4.1 deletePackageX
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
try (PackageFreezer freezer = mPm.freezePackageForDelete(
packageName, freezeUser, deleteFlags,
"deletePackageX", ApplicationExitInfo.REASON_OTHER)) {
res = deletePackageLIF(packageName, UserHandle.of(removeUser),
true, allUsers,
deleteFlags | PackageManager.DELETE_CHATTY,
info, true, callingUid);
}
}获取 mInstallLock 后冻结目标包,防止卸载期间进程继续依赖即将移除的 code/data。deletePackageLIF 返回后 freezer 通过 try-with-resources 释放;冻结失败或动作返回 false 都不会发送成功广播。
4.2 删除动作
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
public boolean deletePackageLIF(String packageName, UserHandle user,
boolean deleteCodeAndResources, int[] allUserHandles, int flags,
PackageRemovedInfo outInfo, boolean writeSettings, int callingUid) {
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
if (ps == null) {
return false;
}
final PackageSetting disabledPs =
mPm.mSettings.getDisabledSystemPkgLPr(ps);
final DeletePackageAction action = mayDeletePackageLocked(
outInfo, ps, disabledPs, flags, user, callingUid);
if (action == null) {
return false;
}
executeDeletePackageLIF(action, packageName,
deleteCodeAndResources, allUserHandles,
writeSettings, false);
return true;
}DeletePackageAction 把删除前读取的 PackageSetting、disabled system setting、flags、user 和 caller uid 固定下来。动作创建在 mLock 下,执行在 mInstallLock 下;这样后续资源清理不用重新猜测删除对象。
5. 系统包分支
5.1 恢复预装版本
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
if (systemApp) {
// 删除更新资源并回到 system partition 的 APK
deleteInstalledSystemPackage(action, allUserHandles, writeSettings);
mPm.restoreDisabledSystemPackageLIF(
action, allUserHandles, writeSettings);
} else {
deleteInstalledPackageLIF(action, allUserHandles,
deleteCodeAndResources, flags, outInfo, writeSettings);
}系统更新的 APK 位于 data 分区,删除它不会触碰只读分区原 APK。restoreDisabledSystemPackageLIF 会启用 disabled setting、删除升级包 native binaries、重新扫描原路径并恢复用户安装状态。若系统包没有 disabled 记录,mayDeletePackageLocked 会拒绝未知系统包的删除。
5.2 版本与数据策略
系统原包版本低于 data 更新包时,恢复路径会根据版本和 app id 决定是否清除数据;如果回退可能造成数据不兼容,DELETE_KEEP_DATA 会被清除。这个判断在 deleteInstalledSystemPackage 中完成,不能假设卸载更新永远保留 /data/user。
6. 普通包清理
6.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();PackageRemovedInfo 在状态被破坏前保存广播用户、数据是否删除、包名和版本。它既是广播构造输入,也是卸载 observer 的结果载体;因此必须在 mPackages/Settings 改写前填充。
6.2 广播与资源顺序
源码文件: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());
}源码明确把广播放在资源最终删除之前。广播消费者可以读取 PackageRemovedInfo 对应的旧状态;随后才清理延迟 code path、dexopt 或 native 资源。DELETE_KEEP_DATA 只影响数据删除标记,不会阻止包状态和 code path 的处理。
7. 结果与恢复
7.1 返回值
deletePackageX 成功返回 DELETE_SUCCEEDED,动作失败返回 DELETE_FAILED_INTERNAL_ERROR 或前置检查产生的具体错误码。结果 observer 的发送由后续回调路径完成;客户端收到失败并不意味着文件系统一定没有任何临时目录,需结合清理日志判断。
7.2 安装器删除回调
系统会在 PackageRemovedInfo 准备好后发送 IPackageDeleteObserver2.onPackageDeleted。回调包含包名、删除状态和 message;广播与 observer 的顺序、线程和错误处理由后续卸载回调专题展开。本文关注的是它们都消费同一个删除结果,而不是重新执行删除。
8. 验证方法
8.1 单用户与全用户
在多用户设备安装第三方 APK,仅对用户 0 执行卸载,检查用户 10 仍能通过 pm path 找到 APK;再使用全用户删除,检查 code path 消失且各用户收到 removed 状态。断言分别验证 markPackageUninstalledForUserLPw 和 removeUser=USER_ALL 分支。
8.2 系统更新恢复
更新一个可更新系统应用后执行允许的卸载更新操作,检查 pm path 从 /data/app 回到只读分区,dumpsys package 中 updated-system 状态清除。若 data 仍存在,沿 deleteInstalledSystemPackage、restoreDisabledSystemPackageLIF 和版本/app id 数据策略检查。
8.3 策略失败
对 locked task、App Lock 保护包或非管理员用户删除系统更新,预期分别收到具体失败码且 mPackages、用户 installed 状态不改变。失败发生在 deletePackageVersionedInternal 或 mayDeletePackageLocked 前置检查,不应进入广播成功路径。
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'versionCode|installed=|updated-system'9. 源码路线
PackageManagerShellCommand.runUninstall:确认 shell 参数和 flags。PackageManagerService.deletePackageVersioned:确认 Binder 门面和 observer。DeletePackageHelper.deletePackageVersionedInternal:阅读权限、用户限制、包名归一化。deletePackageX:跟踪版本校验、冻结和删除范围。mayDeletePackageLocked/deletePackageLIF:理解 system/third-party 分支。executeDeletePackageLIF:跟踪PackageRemovedInfo、用户状态和资源清理。restoreDisabledSystemPackageLIF:连接系统应用更新文章的恢复路径。BroadcastHelper.sendPackageRemovedBroadcasts:继续阅读卸载通知消费者。
卸载流程的关键不是“删掉 APK”,而是先决定删除范围,再在锁和 freezer 保护下改变包状态,最后按系统包、其他用户和保留数据条件清理资源并发送通知。掌握 DeletePackageAction 和 PackageRemovedInfo 两个中间对象,就能把一次卸载请求中的调用方、状态 owner、消费者和结束路径串起来。
