Skip to content

包设置状态

解释卸载时 PackageSetting 与每用户状态的字段变化、持久化和消费者更新。

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

包设置状态 ​

本文承接 用户应用卸载、保留数据卸载 和 多用户卸载。前文说明了删除流程如何选择分支;本文只观察 PackageSetting 本身:哪些字段属于包级身份,哪些属于 PackageUserStateImpl,单用户卸载如何重写整组用户字段,保留数据为什么留下 pkg=null,全量删除如何回收 appId,以及内存变化何时进入用户 restrictions、packages settings 和内核映射。

1. 状态分层 ​

1.1 包级状态 ​

PackageSetting 保存包名、real name、appId、code path、version、签名、install source、shared UID、AndroidPackage 引用以及每用户状态表。包级字段决定“这是哪个包、能否接管旧数据”;用户级字段决定“某用户当前能否看到和运行它”。

1.2 用户级状态 ​

PackageUserStateImpl 保存 installed、stopped、notLaunched、enabled state、CE/DE/PCC inode、组件覆盖、archive、suspend 和卸载原因。未显式创建用户状态时,readUserState 返回默认对象;写入时 modifyUserState 创建可观察状态实例。

2. 修改接口 ​

2.1 细粒度 setter ​

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

java
void setEnabled(int state, int userId, String callingPackage) {
    modifyUserState(userId)
            .setEnabledState(state)
            .setLastDisableAppCaller(callingPackage);
    onChanged();
}

void setInstalled(boolean installed, int userId) {
    modifyUserState(userId).setInstalled(installed);
    onChanged();
}

void setArchiveState(ArchiveState archiveState, int userId) {
    modifyUserState(userId).setArchiveState(archiveState);
    onChanged();
}

调用方无需直接操作 mUserStates。setter 先修改目标用户状态,再调用 onChanged();状态变化通过 Settings/PMS 的 watchable 关系使快照消费者失效。onChanged 不是持久化写盘,它只表示内存状态已变化。

2.2 用户状态 setter ​

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

java
public PackageUserStateImpl setInstalled(boolean value) {
    setBoolean(Booleans.INSTALLED, value);
    onChanged();
    return this;
}

public PackageUserStateImpl setStopped(boolean value) {
    setBoolean(Booleans.STOPPED, value);
    onChanged();
    return this;
}

public PackageUserStateImpl setNotLaunched(boolean value) {
    setBoolean(Booleans.NOT_LAUNCHED, value);
    onChanged();
    return this;
}

这些布尔值存放在紧凑的 bit field 中。setter 自身也发出 change notification,随后 PackageSetting 包装方法再次通知上层;消费者不应持有可变状态引用,而应通过快照或 readUserState 读取。

3. 单用户卸载 ​

3.1 整体重写 ​

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

java
ps.setUserState(nextUserId,
        ps.getCeDataInode(nextUserId),
        ps.getDeDataInode(nextUserId),
        ps.getPccCeDataInode(nextUserId),
        ps.getPccDeDataInode(nextUserId),
        COMPONENT_ENABLED_STATE_DEFAULT,
        false /* installed */,
        true /* stopped */,
        true /* notLaunched */,
        false /* hidden */,
        0 /* distractionFlags */,
        null /* suspendParams */,
        false /* instantApp */,
        false /* virtualPreload */,
        null /* lastDisableAppCaller */,
        enabledComponents,
        disabledComponents,
        INSTALL_REASON_UNKNOWN,
        UNINSTALL_REASON_UNKNOWN,
        null /* harmfulAppWarning */,
        null /* splashScreenTheme */,
        firstInstallTime,
        USER_MIN_ASPECT_RATIO_UNSET,
        archiveState,
        appLockEnablementState,
        VIRTUAL_GAMEPAD_USER_OPTION_UNSET,
        PERSONAL_CONTEXT_MODE_UNSET);

源码不是只调用 setInstalled(false),而是一次重建目标用户状态。enabled state 被重置为 DEFAULT,stopped 与 notLaunched 变为 true,hidden/suspend/instant/virtual preload 等状态清除,install/uninstall reason 重置为 UNKNOWN。CE/DE/PCC inode 是否继续有效取决于数据是否保留和后续数据清理。

3.2 条件保留字段 ​

java
if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
    enabledComponents = new ArraySet<>(
            ps.readUserState(nextUserId).getEnabledComponents());
    disabledComponents = new ArraySet<>(
            ps.readUserState(nextUserId).getDisabledComponents());
}

final long firstInstallTime =
        (flags & DELETE_KEEP_DATA) == 0
                ? 0
                : ps.getUserStateOrDefault(nextUserId)
                        .getFirstInstallTimeMillis();

只有 DELETE_KEEP_DATA 才保留 enabled/disabled component 集合、首次安装时间、archive state 和可选 App Lock 状态。注意这里保留的是组件覆盖集合,不是 package enabled state;后者仍被设为 DEFAULT。

3.3 字段矩阵 ​

字段普通单用户卸载DELETE_KEEP_DATA
installedfalsefalse
stoppedtruetrue
notLaunchedtruetrue
enabled stateDEFAULTDEFAULT
enabled/disabled components清除保留副本
CE/DE inode数据销毁后设为 -1保留
firstInstallTime0保留
归档状态和 App Lock清除按条件保留
suspend/hidden/instant清除清除

4. Inode 状态 ​

4.1 数据销毁后 ​

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

java
mAppDataHelper.destroyAppDataLIF(
        resolvedPkg, userId, appDataDeletionFlags);
if (userId != UserHandle.USER_ALL) {
    synchronized (mPm.mLock) {
        ps.setCeDataInode(-1, userId);
        ps.setDeDataInode(-1, userId);
        ps.setPccCeDataInode(-1, userId);
        ps.setPccDeDataInode(-1, userId);
    }
}

inode 是数据目录身份的缓存。installd 完成目标用户数据销毁后,PMS 将四类 inode 设为 -1;保留数据分支在 destroy 之前返回,因此 inode 不变。USER_ALL 删除通常随后移除整个 setting,不需要逐用户写 -1。

4.2 dataExists 消费 ​

java
boolean hasDataOnAnyOtherUser(int[] allUsers, int currentUser) {
    for (int user : allUsers) {
        if (user == currentUser) {
            continue;
        }
        if (readUserState(user).dataExists()) {
            return true;
        }
    }
    return false;
}

dataExists() 读取这些用户数据状态,供 shouldDeletePackageSetting 判断是否可以彻底删除 appId。installed=false 不代表 dataExists=false,这是多用户和 -k 场景保留 setting 的核心依据。

5. 运行对象 ​

5.1 pkg=null ​

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

java
removePackageLI(packageName,
        (flags & PackageManager.DELETE_CHATTY) != 0);
if (!deletedPs.isSystem()) {
    // PackageSetting 可能因 KEEP_DATA 保留,但旧 APK 已不可运行
    deletedPs.setPkg(null);
}

mPackages 移除后,保留的第三方 PackageSetting 必须清空 pkg。包级 identity、签名、version 和 appId 仍在,但 Resolver、组件查询和启动路径不再拥有 AndroidPackage。这是一种设计允许的持久状态,不是空指针异常。

5.2 split 元数据 ​

java
if (deletedPkg != null && deletedPkg.getSplitNames() != null) {
    deletedPs.setSplitNames(deletedPkg.getSplitNames());
    deletedPs.setSplitRevisionCodes(
            deletedPkg.getSplitRevisionCodes());
}

在 pkg 清空之前保存 split 名称和 revision,供以后重装时做降级检查。base version、base revision、签名也继续留在 setting 中;新 APK 不能把保留数据状态当作完全新装绕过校验。

6. 全量删除 ​

6.1 setting 与 appId ​

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

java
boolean removePackageAndAppIdLPw(String name) {
    final PackageSetting p = mPackages.remove(name);
    if (p != null) {
        removeInstallerPackageStatus(name);
        removePccIdLPw(p.getPccId());
        final SharedUserSetting sharedUser =
                getSharedUserSettingLPr(p);
        if (sharedUser != null) {
            sharedUser.removePackage(p);
            return checkAndPruneSharedUserLPw(
                    sharedUser, false);
        } else {
            removeAppIdLPw(p.getAppId());
            return true;
        }
    }
    return false;
}

非 shared UID 包直接释放 appId;shared UID 包先从 shared user 移除,只有 shared user 可裁剪时才回收公共 appId。返回值写入 PackageRemovedInfo.mIsAppIdRemoved,决定 UID removed 等后续通知。

6.2 删除条件 ​

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

全量用户删除且不保留数据时 setting 才必然删除;单用户作为最后 installed 用户时,还要确认其他用户没有残留数据。这个判断把 appId 生命周期与真实数据 owner 绑定起来。

7. 系统包恢复 ​

7.1 enabled 状态还原 ​

删除系统应用更新前,deletePackageX 保存每个用户的 enabled state、last disable caller 和 installed 状态。恢复预装包后再逐用户写回 enabled 状态,并据此判断 compressed stub 是否需要重新启用。这里不能套用普通第三方卸载的 DEFAULT 重置作为最终结果。

7.2 安装集合 ​

restoreDisabledSystemPackageLIF 使用 PackageRemovedInfo.mOrigUsers 恢复 system setting 的 installed 集合;未安装用户保持 false。系统包恢复因此会经历“删除更新时的临时状态重写”和“原包重扫后的状态还原”两个阶段。

8. 持久化 ​

8.1 用户限制文件 ​

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

java
void scheduleWritePackageRestrictions(int userId) {
    invalidatePackageInfoCache(
            PackageMetrics.INVALIDATION_REASON_SCHEDULE_WRITE_PACKAGE_RESTRICTIONS);
    synchronized (mDirtyUsers) {
        if (userId == USER_ALL) {
            for (int id : mUserManager.getUserIds()) {
                mDirtyUsers.add(id);
            }
        } else if (mUserManager.exists(userId)) {
            mDirtyUsers.add(userId);
        }
    }
    if (!mBackgroundHandler.hasMessages(
            WRITE_DIRTY_PACKAGE_RESTRICTIONS)) {
        mBackgroundHandler.sendMessageDelayed(
                mBackgroundHandler.obtainMessage(
                        WRITE_DIRTY_PACKAGE_RESTRICTIONS, this),
                WRITE_SETTINGS_DELAY);
    }
}

单用户状态通过 dirty-user 队列延迟写入对应 package-restrictions.xml,同时使 package info cache 失效。内存查询可先看到新状态,磁盘写入稍后完成;故障诊断需区分内存已更新和 restrictions 尚未落盘。

8.2 全局 settings ​

removePackageDataLIF 在 writeSettings=true 时调用 writeSettingsLPrTEMP() 保存全局包设置;系统包恢复还可能调用 writeAllUsersPackageRestrictionsLPr()。writeKernelMappingLPr 则更新 appId/用户安装映射,服务于内核和 installd 消费者。三类写入不是同一个文件,也不保证同一时刻完成。

9. 验证方法 ​

9.1 单用户字段 ​

设置非默认 enabled state、组件覆盖、suspend 和首次安装时间,然后执行普通单用户卸载与 -k 卸载。比较 dumpsys package:两者均应 installed=false、stopped=true;package enabled state 回到 DEFAULT,只有 -k 保留组件集合和首次安装时间。

9.2 Inode 与数据 ​

普通卸载后检查目标用户 CE/DE inode 变为无效;-k 后 inode 和数据目录保持。再让其他用户仅保留数据而非 installed,确认最后 installed 用户卸载时 setting/appId 仍不会被删除。

9.3 pkg=null ​

全用户 -k 后检查包不能被 pm path 或组件解析找到,但 Settings 中仍有 version、signing 和 appId。用错误签名重装应失败,正确签名重装后 pkg 重新指向新 AndroidPackage。

9.4 持久化重启 ​

单用户卸载后等待 restrictions 写入并重启设备,确认 installed/stopped 状态保留;在写入前强制终止 system_server 的实验只适用于测试环境,可用于观察 dirty write 窗口,不应在生产设备执行。

10. 源码路线 ​

  1. PackageSetting 的 readUserState、modifyUserState 和 setter:理解状态 owner。
  2. PackageUserStateImpl:查看 bit field、inode、archive 和 change notification。
  3. markPackageUninstalledForUserLPw:逐字段核对卸载后的真实值。
  4. clearPackageStateForUserLIF:把数据销毁与 inode 失效连接起来。
  5. removePackageDataLIF:理解 pkg=null、split 保存和 setting 删除条件。
  6. Settings.removePackageAndAppIdLPw:跟踪普通 appId 与 shared UID 回收。
  7. scheduleWritePackageRestrictions、writeKernelMappingLPr:理解内存、用户文件和内核映射的生效时机。

卸载后的 PackageSetting 不是简单“存在或不存在”:它可以仍是 live package,也可以只保留数据身份,还可能在系统包恢复中被替换为预装设置。用户状态的整组重写、inode、pkg 引用、appId 和持久化文件分别服务不同消费者。逐字段跟踪这些状态,才能可靠判断一次卸载后系统究竟保留了什么,以及重启或重装后为什么会得到当前结果。