Skip to content

卸载全流程

追踪 Android 17 从 pm uninstall 到 DeletePackageHelper、用户状态、系统包恢复和卸载通知的完整路径。

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

卸载全流程 ​

本文面向已经读过 系统应用更新、安装回调 的读者。主题是一个卸载请求如何从 shell/Binder 进入 DeletePackageHelper,再根据用户范围、系统包状态、保留数据和策略限制选择不同动作。这里不展开数据目录清理的每个 installd 参数,那是 数据清理流程 的主题;本文只追踪卸载决策、状态变化、广播和恢复入口。

1. 入口分叉 ​

1.1 Shell 命令 ​

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

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

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

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

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

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

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

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

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

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

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 前置检查,不应进入广播成功路径。

bash
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'versionCode|installed=|updated-system'

9. 源码路线 ​

  1. PackageManagerShellCommand.runUninstall:确认 shell 参数和 flags。
  2. PackageManagerService.deletePackageVersioned:确认 Binder 门面和 observer。
  3. DeletePackageHelper.deletePackageVersionedInternal:阅读权限、用户限制、包名归一化。
  4. deletePackageX:跟踪版本校验、冻结和删除范围。
  5. mayDeletePackageLocked / deletePackageLIF:理解 system/third-party 分支。
  6. executeDeletePackageLIF:跟踪 PackageRemovedInfo、用户状态和资源清理。
  7. restoreDisabledSystemPackageLIF:连接系统应用更新文章的恢复路径。
  8. BroadcastHelper.sendPackageRemovedBroadcasts:继续阅读卸载通知消费者。

卸载流程的关键不是“删掉 APK”,而是先决定删除范围,再在锁和 freezer 保护下改变包状态,最后按系统包、其他用户和保留数据条件清理资源并发送通知。掌握 DeletePackageAction 和 PackageRemovedInfo 两个中间对象,就能把一次卸载请求中的调用方、状态 owner、消费者和结束路径串起来。