Skip to content

卸载后广播

解释卸载完成后 PACKAGE_REMOVED、FULLY_REMOVED、UID_REMOVED 和系统包恢复广播的发送条件。

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

卸载后广播 ​

本文承接 卸载回调、多用户卸载 和 包设置状态。observer 只告诉卸载发起方结果;广播则通知 PackageMonitor、installer、系统服务和其他可见应用更新自己的缓存。本文沿 Android 17 BroadcastHelper 解释 PackageRemovedInfo 如何决定广播类型、extras、用户和接收范围,并说明系统应用卸载更新为什么紧接着又发送 ADDED/REPLACED。

1. 发送入口 ​

1.1 删除成功后 ​

源码文件: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);
    PackageMetrics.onUninstallSucceeded(info, deleteFlags, removeUser);
}

// 广播之后才最终清理旧 code path
if (info.mArgs != null) {
    mRemovePackageHelper.cleanUpResources(
            info.mArgs.getPackageName(), info.mArgs.getCodeFile());
}

只有 deletePackageLIF 返回成功才发送 removed 广播。调用发生在主要删除锁段之后、旧 code path 最终清理之前,让接收者先处理包状态变化。前置策略失败只通过 observer 返回错误,不发送虚假的 PACKAGE_REMOVED。

1.2 数据载体 ​

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

java
final class PackageRemovedInfo {
    String mRemovedPackage;
    String mInstallerPackageName;
    int mUid = -1;
    boolean mIsAppIdRemoved;
    int[] mRemovedUsers;
    int[] mBroadcastUsers;
    int[] mInstantUserIds;
    boolean mIsRemovedPackageSystemUpdate;
    boolean mIsUpdate;
    boolean mDataRemoved;
    boolean mRemovedForAllUsers;
    boolean mIsStaticSharedLib;
    SparseArray<int[]> mBroadcastAllowList;
}

PackageRemovedInfo 在包状态被修改前收集事实。BroadcastHelper 不重新查询已经删除的包,而是消费这些字段;否则 instant-app 类型、原 UID 和删除前的可见性信息可能已经丢失。

2. 广播 extras ​

2.1 基础字段 ​

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

java
final Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, packageRemovedInfo.mUid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved);
extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL,
        isRemovedPackageSystemUpdate);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);

final boolean isReplace = isUpdate
        || isRemovedPackageSystemUpdate;
if (isReplace || isArchived) {
    extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
if (isArchived) {
    extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
}
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS,
        isRemovedForAllUsers);

EXTRA_DATA_REMOVED=false 表示 -k 或其他保留数据路径;EXTRA_REMOVED_FOR_ALL_USERS 描述全局可见性,不等于当前广播一定发给所有用户。系统更新回退和 archive 都设置 EXTRA_REPLACING,告诉消费者包还会以另一种形式存在。

2.2 UID 的用户修正 ​

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

java
for (int userId : userIds) {
    final Intent intent = new Intent(action,
            pkg != null
                    ? Uri.fromParts(PACKAGE_SCHEME, pkg, null)
                    : null);
    intent.putExtras(extras);

    int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
    if (uid >= 0 && UserHandle.getUserId(uid) != userId) {
        uid = UserHandle.getUid(
                userId, UserHandle.getAppId(uid));
        intent.putExtra(Intent.EXTRA_UID, uid);
    }
    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
            | flags);
    broadcastIntent(intent, finishedReceiver, isInstantApp,
            userId, broadcastAllowList, filterExtrasForReceiver,
            bOptions, requiredPermissions);
}

PackageRemovedInfo.mUid 可能按某个用户或 appId 形式记录。真正逐用户发送时,BroadcastHelper 重新组合 userId 与 appId,保证用户 10 收到的 UID 不会错误指向用户 0。

3. PACKAGE_REMOVED ​

3.1 Installer 通知 ​

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

java
if (removedPackage != null && installerPackageName != null) {
    sendPackageBroadcastAndNotify(
            Intent.ACTION_PACKAGE_REMOVED,
            removedPackage, extras, 0,
            installerPackageName,
            null, broadcastUserIds, instantUserIds,
            null, null);
}

installer 获得一份定向广播,不受普通 receiver 的 visibility allowlist 限制。它需要更新自己的安装记录,即使当前不在公开广播接收集合中。

3.2 普通与内部通知 ​

java
if (removedPackage != null) {
    sendPackageBroadcastAndNotify(
            Intent.ACTION_PACKAGE_REMOVED,
            removedPackage, extras, 0,
            null, null, broadcastUserIds, instantUserIds,
            broadcastAllowList, null);

    sendPackageBroadcastAndNotify(
            Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
            removedPackage, extras, 0,
            PLATFORM_PACKAGE_NAME, null,
            broadcastUserIds, instantUserIds,
            broadcastAllowList, null);
}

第一条面向符合包可见性规则的普通接收者,第二条只发给 platform package。内部广播还会携带该用户对应的 visibility allowlist,供系统内部消费者准确处理已删除包。

3.3 静态共享库 ​

java
if (isStaticSharedLib) {
    // 静态共享库只通知 installer,没有其他预期接收者
    return;
}

静态共享库使用合成包名且不作为普通应用生命周期暴露,因此定向 installer 广播后立即返回,不发送普通 removed、fully removed 或 UID removed。这是判断广播缺失时必须先检查的包类型条件。

4. FULLY_REMOVED ​

4.1 发送条件 ​

java
if (dataRemoved && !isRemovedPackageSystemUpdate) {
    sendPackageBroadcastAndNotify(
            Intent.ACTION_PACKAGE_FULLY_REMOVED,
            removedPackage, extras,
            Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
            null, null, broadcastUserIds, instantUserIds,
            broadcastAllowList, null);
    packageSender.notifyPackageRemoved(
            removedPackage, packageRemovedInfo.mUid);
}

条件必须同时满足:应用数据已经删除,并且此次不是卸载系统应用更新。pm uninstall -k 不发送 fully removed;系统更新回退即使 data 更新 APK 被删除,预装包仍然存在,也不属于完全移除。该广播允许后台接收者运行,并同步触发 PackageSender.notifyPackageRemoved。

5. UID_REMOVED ​

5.1 appId 回收 ​

java
if (packageRemovedInfo.mIsAppIdRemoved) {
    if (isReplace || isArchived) {
        extras.putString(Intent.EXTRA_PACKAGE_NAME,
                removedPackage);
    }
    sendPackageBroadcastAndNotify(
            Intent.ACTION_UID_REMOVED,
            null, extras,
            Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
            null, null, broadcastUserIds, instantUserIds,
            broadcastAllowList, null);
}

UID_REMOVED 取决于 Settings.removePackageAndAppIdLPw 是否真的回收 appId。shared UID 仍被其他包使用、DELETE_KEEP_DATA 保留 setting、或系统包恢复时,都可能不发送。replace/archive 时额外携带 package name,避免 AppOps reset 在不知道包名时扩大到整个 UID 集合。

6. 用户与可见性 ​

6.1 用户分组 ​

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

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

分类在用户状态被清除前完成。普通用户按 broadcastAllowList 过滤;instant app 广播单独发送,并要求接收者拥有 ACCESS_INSTANT_APPS。

6.2 运行用户默认值 ​

java
final int[] resolvedUserIds;
if (userIds == null) {
    resolvedUserIds = ActivityManager.getService()
            .getRunningUserIds();
} else {
    resolvedUserIds = userIds;
}

某些系统包恢复通知传入 userIds=null,此时只向当前 running users 发送。普通卸载使用 PackageRemovedInfo 中明确的用户数组。不能把 null 理解为 USER_ALL。

6.3 可见性 allowlist ​

普通广播调用 ActivityManagerInternal.broadcastIntentWithCallback 时传入每用户 allowlist。Android 包可见性规则因此不仅影响查询 API,也影响谁能收到包生命周期广播;installer 定向通知和 platform 内部通知有各自路径。

7. 系统包恢复 ​

7.1 恢复广播 ​

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

java
void sendSystemPackageUpdatedBroadcasts(
        PackageRemovedInfo info) {
    if (!info.mIsRemovedPackageSystemUpdate) {
        return;
    }
    final Bundle extras = new Bundle(2);
    extras.putInt(Intent.EXTRA_UID, info.mUid);
    extras.putBoolean(Intent.EXTRA_REPLACING, true);

    sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED,
            info.mRemovedPackage, extras, 0,
            null, null, null, null,
            info.mBroadcastAllowList, null);
    sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED,
            info.mRemovedPackage, extras, 0,
            null, null, null, null,
            info.mBroadcastAllowList, null);
    sendPackageBroadcastAndNotify(Intent.ACTION_MY_PACKAGE_REPLACED,
            null, null, 0, info.mRemovedPackage,
            null, null, null, null,
            getTemporaryBroadcastOptionsForSystemPackageUpdate(
                    REASON_PACKAGE_REPLACED).toBundle());
}

卸载 data 更新后,预装 system 包重新生效,所以消费者先看到 removed(带 system-update-uninstall/replacing),随后看到 added、replaced;包自身收到定向 MY_PACKAGE_REPLACED,并获得短暂后台启动 allowlist。若存在 installer,还会收到定向 ADDED 与 REPLACED。

8. PackageMonitor ​

8.1 投递顺序 ​

java
private void sendPackageBroadcastAndNotify(...) {
    mHandler.post(() -> {
        sendPackageBroadcast(action, pkg, extras, flags,
                targetPkg, finishedReceiver, userIds,
                instantUserIds, broadcastAllowList,
                null, bOptions, null);

        // 广播先投递,再通知 PackageMonitor,避免进程重启竞态
        if (targetPkg == null) {
            notifyPackageMonitor(action, pkg, extras,
                    userIds, instantUserIds,
                    broadcastAllowList, null);
        }
    });
}

源码明确要求 PackageMonitor 在广播发送后通知。例如 update/replaced 期间,若 monitor 先重启应用,而广播处理又杀进程,会出现重启后再次死亡的竞态。定向 installer 广播不会重复通知 monitor,只有 targetPkg==null 的公共分发触发一次。

9. 时序总图 ​

10. 验证方法 ​

10.1 完全卸载 ​

完全删除普通第三方包,记录 action 与 extras。预期出现 PACKAGE_REMOVED、PACKAGE_REMOVED_INTERNAL、PACKAGE_FULLY_REMOVED;appId 真正回收时还出现 UID_REMOVED。检查 EXTRA_DATA_REMOVED=true 和正确的 per-user UID。

10.2 保留数据 ​

执行 pm uninstall -k。预期 PACKAGE_REMOVED 中 data removed 为 false,不发送 FULLY_REMOVED,通常也不发送 UID_REMOVED,因为 PackageSetting/appId 仍保留。

10.3 系统更新回退 ​

卸载系统应用更新,预期 removed 广播带 replacing 与 system-update-uninstall,随后收到 ADDED、REPLACED,包自身收到 MY_PACKAGE_REPLACED;不得出现 FULLY_REMOVED。

10.4 多用户与 instant ​

分别删除普通用户和 instant-app 用户,检查 user handle、重组后的 UID 和 ACCESS_INSTANT_APPS 限制。再让一个不可见应用注册 receiver,确认 visibility allowlist 会阻止其收到公共广播,而 installer 定向通知仍可到达。

11. 源码路线 ​

  1. DeletePackageHelper.deletePackageX:确认广播与资源清理的调用顺序。
  2. PackageRemovedInfo:理解用户、UID、data、appId 和包类型字段。
  3. sendPackageRemovedBroadcasts:逐个跟踪 removed、fully removed 和 UID removed 条件。
  4. sendSystemPackageUpdatedBroadcasts:理解系统更新回退后的反向 added/replaced。
  5. sendPackageBroadcast / doSendBroadcast:跟踪 running users、UID 修正和 instant 权限。
  6. sendPackageBroadcastAndNotify:确认 Handler 与 PackageMonitor 顺序。

卸载广播不是一个固定 action,而是对删除结果的条件投影:用户范围决定发送到谁,数据是否删除决定是否 fully removed,appId 是否回收决定是否 UID removed,系统包恢复又把删除变成一次 replacing 生命周期。掌握 PackageRemovedInfo 到 BroadcastHelper 的映射后,才能从一组广播准确反推 PMS 实际执行了哪种卸载。