Skip to content

多用户卸载

解释单用户、全用户、profile 级联和阻止卸载条件下的包状态与资源变化。

基于android-17.0.0_r1
AndroidPackageManagerService多用户卸载源码阅读

多用户卸载 ​

本文承接 用户应用卸载、保留数据卸载 和 卸载回调。Android 的 APK code 通常由所有用户共享,但 installed、数据目录、权限和组件状态按用户保存。因此“为用户 10 卸载”与“从设备删除包”不是同一件事。本文沿 Android 17 源码追踪目标用户集合如何产生、单用户删除何时升级为全量清理、profile 如何级联、block uninstall 为何可能造成部分成功,以及广播如何按普通用户与 instant user 分组。

1. 多用户模型 ​

1.1 全局与用户状态 ​

对象作用域典型 owner
APK/code path设备全局PackageSetting、文件系统
appId设备全局Settings AppIds
AndroidPackage设备全局运行对象mPackages
installed/stopped/enabled每用户PackageUserState
CE/DE data每用户AppData/installd
runtime permission每用户PermissionManager

单用户卸载首先改变 per-user 状态。只有没有其他用户需要 code、且没有必须保留的数据或设置时,PMS 才删除全局对象。

2. 目标用户 ​

2.1 用户数组 ​

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

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

users 是策略检查的目标集合。跨用户删除需要 INTERACT_ACROSS_USERS_FULL;DELETE_ALL_USERS 并不是简单把 userId 改成 USER_ALL 后跳过权限检查。后续 device admin、protected data 和 block uninstall 都基于这个集合评估。

2.2 设备策略遍历 ​

java
for (int user : users) {
    if (mPm.isPackageDeviceAdmin(packageName, user)) {
        observer.onPackageDeleted(packageName,
                PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER,
                null);
        return;
    }
    if (mPm.mProtectedPackages.isPackageDataProtected(
            user, packageName)) {
        observer.onPackageDeleted(packageName,
                PackageManager.DELETE_FAILED_INTERNAL_ERROR,
                null);
        return;
    }
}

active device admin 或 protected package data 对目标集合中的任一用户成立时,入口直接拒绝整次请求。此时不会像 block uninstall 分支那样删除部分用户;两类策略的原子性不同。

3. 单用户卸载 ​

3.1 状态先变更 ​

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

java
if (userId != USER_ALL) {
    synchronized (mPm.mLock) {
        markPackageUninstalledForUserLPw(
                ps, user, flags, callingUid);
        if (!systemApp) {
            clearPackageStateAndReturn =
                    ps.isInstalledOnAnyOtherUser(
                            mUserManagerInternal.getUserIds(), userId)
                    || mPm.shouldKeepUninstalledPackageLPr(packageName);
        } else {
            clearPackageStateAndReturn = true;
        }
    }
    if (clearPackageStateAndReturn) {
        mRemovePackageHelper.clearPackageStateForUserLIF(
                ps, userId, flags);
        mPm.scheduleWritePackageRestrictions(user);
        return;
    }
}

目标用户先被标记为未安装。其他用户仍 installed 时,仅清理目标用户的数据、权限、域名和 preferred activities,保留共享 APK、appId 和全局包对象。若这是最后一个安装用户,流程继续检查是否还有其他用户数据,再决定是否删除 PackageSetting。

3.2 遗留数据 ​

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

java
private static boolean shouldDeletePackageSetting(
        PackageSetting deletedPs, int userId,
        int[] allUserHandles, int flags) {
    if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
        return false;
    }
    if (userId == UserHandle.USER_ALL) {
        return true;
    }
    if (deletedPs.hasDataOnAnyOtherUser(
            allUserHandles, userId)) {
        return false;
    }
    return true;
}

最后一个 installed 用户被删除后,其他用户仍可能留有数据,例如先前使用 -k 卸载。只要 hasDataOnAnyOtherUser 为 true,PMS 就保留 PackageSetting,避免释放 appId 后留下无法安全归属的数据。

4. Profile 级联 ​

4.1 父用户成功后触发 ​

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

java
returnCode = deletePackageX(internalPackageName, versionCode,
        userId, deleteFlags, false, callingUid);

if (returnCode == DELETE_SUCCEEDED && packageState != null) {
    final int[] childUserIds =
            mUserManagerInternal.getProfileIds(userId, true);
    for (int childId : childUserIds) {
        if (childId == userId) {
            continue;
        }
        if (mUserManagerInternal.getProfileParentId(childId) != userId) {
            continue;
        }
        if (!packageState.getUserStateOrDefault(childId)
                .isInstalled()) {
            continue;
        }
        final UserProperties properties =
                mUserManagerInternal.getUserProperties(childId);
        if (properties != null
                && properties.getDeleteAppWithParent()) {
            final int childResult = deletePackageX(
                    internalPackageName, versionCode, childId,
                    deleteFlags, false, callingUid);
            if (childResult != DELETE_SUCCEEDED) {
                returnCode =
                        PackageManager.DELETE_FAILED_FOR_CHILD_PROFILE;
            }
        }
    }
}

级联的前置条件有四个:父用户删除成功、确实是直接 child profile、child 中包已安装、deleteAppWithParent=true。源码使用删除前取得的 packageState 判断 child 是否安装;child 删除仍走完整的 deletePackageX 策略与数据清理。

4.2 失败语义 ​

父用户已经成功后,某个 child 删除失败不会恢复父用户;最终 observer 返回 DELETE_FAILED_FOR_CHILD_PROFILE。这是部分完成结果,调用方必须逐用户查询 installed 状态,而不能只把非成功码解释为“所有用户均未改变”。

5. 全用户卸载 ​

5.1 无阻止用户 ​

java
final int[] blocked = getBlockUninstallForUsers(
        innerSnapshot, internalPackageName, users);
if (ArrayUtils.isEmpty(blocked)) {
    returnCode = deletePackageX(internalPackageName,
            versionCode, userId, deleteFlags,
            false, callingUid);
}

无人阻止时,保留 DELETE_ALL_USERS 调用一次 deletePackageX。内部 removeUser=USER_ALL,PackageRemovedInfo.mRemovedUsers 会收集所有 installed 或仍有数据的用户,数据和包设置按全局范围清理。

5.2 部分阻止 ​

java
} else {
    final int userFlags =
            deleteFlags & ~PackageManager.DELETE_ALL_USERS;
    for (int targetUserId : users) {
        if (!ArrayUtils.contains(blocked, targetUserId)) {
            final int result = deletePackageX(
                    internalPackageName, versionCode,
                    targetUserId, userFlags,
                    false, callingUid);
            if (result != DELETE_SUCCEEDED) {
                Slog.w(TAG, "Package delete failed for user "
                        + targetUserId + ", returnCode " + result);
            }
        }
    }
    returnCode = PackageManager.DELETE_FAILED_OWNER_BLOCKED;
}

存在 block user 时,源码清除 DELETE_ALL_USERS,对其他用户逐个执行单用户删除,最后固定返回 owner blocked。共享 APK 是否仍存在取决于被阻止用户是否继续安装或拥有数据;这条路径刻意不是全有或全无。

5.3 block 集合 ​

java
private int[] getBlockUninstallForUsers(
        Computer snapshot, String packageName, int[] userIds) {
    int[] result = EMPTY_INT_ARRAY;
    for (int userId : userIds) {
        if (snapshot.getBlockUninstallForUser(
                packageName, userId)) {
            result = ArrayUtils.appendInt(result, userId);
        }
    }
    return result;
}

block uninstall 是 per-user 状态,由 Computer snapshot 读取。全用户请求必须先形成完整 blocked 集合,才能决定一次全量调用还是多个单用户调用。

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

mRemovedUsers 在状态修改前确定。全用户删除不仅包含 installed 用户,还包含仍有数据的用户;单用户删除只包含目标用户。该数组供广播、metrics 和 sequence number 更新共同消费。

6.2 广播分组 ​

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

java
public void populateBroadcastUsers(
        PackageSetting deletedPackageSetting) {
    if (mRemovedUsers == null) {
        mBroadcastUsers = null;
        return;
    }
    mBroadcastUsers = EMPTY_INT_ARRAY;
    mInstantUserIds = EMPTY_INT_ARRAY;
    for (int i = mRemovedUsers.length - 1; i >= 0; --i) {
        final int userId = mRemovedUsers[i];
        if (deletedPackageSetting.getInstantApp(userId)) {
            mInstantUserIds = ArrayUtils.appendInt(
                    mInstantUserIds, userId);
        } else {
            mBroadcastUsers = ArrayUtils.appendInt(
                    mBroadcastUsers, userId);
        }
    }
}

广播按用户原来的 instant-app 状态拆分为两组。这个分类必须在 markPackageUninstalledForUserLPw 清除 instant 状态前完成,否则广播消费者会丢失原始类型。

7. 时序总图 ​

8. 验证方法 ​

8.1 其他用户仍安装 ​

在用户 0、10 安装同一第三方包,只删除用户 0。断言用户 0 的 installed=false、数据与权限清理,用户 10 仍可启动,APK code path 和 appId 保留。这验证“code 全局、安装状态 per-user”。

8.2 最后用户 ​

先让用户 10 使用 -k 卸载留下数据,再删除用户 0 的最后一个 installed 状态。断言 hasDataOnAnyOtherUser 阻止删除 PackageSetting/appId;随后完整清理用户 10 数据,才允许全局设置消失。

8.3 Profile 级联 ​

建立 parent 与两个 child profile,一个设置 deleteAppWithParent=true,另一个为 false。删除 parent 后,断言只有前者级联;注入 child 删除失败时,observer 返回 DELETE_FAILED_FOR_CHILD_PROFILE,parent 仍保持已删除。

8.4 部分阻止 ​

为一个用户设置 block uninstall,再发起 DELETE_ALL_USERS。断言未阻止用户被逐个卸载,阻止用户保留,最终结果为 DELETE_FAILED_OWNER_BLOCKED。这是本文最重要的非原子分支。

bash
adb shell pm list users
adb shell pm list packages --user <userId> | grep <package.name>
adb shell dumpsys package <package.name> | grep -E 'User|installed=|dataDir'

9. 源码路线 ​

  1. deletePackageVersionedInternal:目标 users、跨用户权限和策略遍历。
  2. deletePackageX:removeUser、用户状态快照和 freezer 范围。
  3. executeDeletePackageLIF:单用户 installed 状态与全量删除分叉。
  4. shouldDeletePackageSetting:理解其他用户数据为何保留 appId。
  5. parent/child profile 循环:跟踪 deleteAppWithParent 与部分失败。
  6. getBlockUninstallForUsers:跟踪全用户请求的部分执行。
  7. PackageRemovedInfo.populateBroadcastUsers:理解广播用户集合。

多用户卸载的关键不是 USER_ALL 常量,而是区分三层状态:共享的 APK/appId、每用户 installed/data、profile 和设备策略关系。DeletePackageHelper 先形成目标用户集合,再按策略决定一次全量删除或多次单用户删除;RemovePackageHelper 最后依据其他用户的数据决定是否真正移除 PackageSetting。沿着这三层阅读,才能正确解释最后一个用户、孤立数据、profile 级联和部分阻止场景。