Skip to content

DeletePackageHelper 入口

追踪 DeletePackageHelper 的入口校验、删除动作构造、锁边界和执行分支。

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

DeletePackageHelper 入口 ​

本文承接 卸载全流程。上一文从用户命令一路讲到系统包恢复和广播;本文把镜头收窄到 DeletePackageHelper,回答三个源码阅读问题:入口怎样把外部参数变成内部删除请求,DeletePackageAction 为什么要在锁内创建,以及单用户、全用户、静默卸载和 profile 级联如何改变控制流。读完后,读者可以从 deletePackageVersionedInternal 继续走到 deletePackageX、deletePackageLIF 和 executeDeletePackageLIF,并知道每一步由谁拥有状态、何时异步、失败后谁负责通知。

1. 类边界 ​

1.1 依赖注入 ​

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

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生效时机
入口deletePackageVersionedInternalBinder caller + PMS snapshot同步校验后异步投递
编排deletePackageXDeletePackageHelper拿锁、冻结、调用 LIF
决策mayDeletePackageLockedSettings/PackageSettingmLock 内构造 action
执行deletePackageLIFDeletePackageHelpermInstallLock 内改包状态
资源executeDeletePackageLIF、RemovePackageHelper文件/数据 owner状态修改后清理
通知BroadcastHelper、observer外部消费者删除成功或失败后

2. 入口校验 ​

2.1 参数与权限 ​

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

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 用户范围 ​

java
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

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 调用方判断 ​

java
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

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

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

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 执行分支 ​

java
@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

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 广播与清理 ​

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);
}

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. 源码路线 ​

  1. PackageManagerService.deletePackageVersioned:确认 Binder 门面。
  2. deletePackageVersionedInternal:阅读权限、包名、用户和静默分支。
  3. isCallerAllowedToSilentlyUninstall:理解 installer、system 和设备管理权限。
  4. deletePackageX:跟踪 snapshot、锁、freezer 和结果对象。
  5. mayDeletePackageLocked:理解系统包与 DeletePackageAction 边界。
  6. deletePackageLIF / executeDeletePackageLIF:跟踪单用户、全量和系统包分支。
  7. PackageRemovedInfo、BroadcastHelper、RemovePackageHelper:确认通知和资源清理顺序。

DeletePackageHelper 的核心价值是把“能不能删”“删哪个用户”“删哪一种包”“何时通知”拆成可追踪的阶段。入口负责把外部请求归一化,action 固定删除上下文,LIF 方法在锁和 freezer 保护下改变状态,RemovePackageHelper 与 BroadcastHelper 分别完成资源和通知;这条分层主线比记住一个“卸载函数”更适合继续阅读源码和定位问题。