包设置状态
本文承接 用户应用卸载、保留数据卸载 和 多用户卸载。前文说明了删除流程如何选择分支;本文只观察 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
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
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
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 条件保留字段
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 |
|---|---|---|
| installed | false | false |
| stopped | true | true |
| notLaunched | true | true |
| enabled state | DEFAULT | DEFAULT |
| enabled/disabled components | 清除 | 保留副本 |
| CE/DE inode | 数据销毁后设为 -1 | 保留 |
| firstInstallTime | 0 | 保留 |
| 归档状态和 App Lock | 清除 | 按条件保留 |
| suspend/hidden/instant | 清除 | 清除 |
4. Inode 状态
4.1 数据销毁后
源码文件:frameworks/base/services/core/java/com/android/server/pm/RemovePackageHelper.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 消费
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
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 元数据
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
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 删除条件
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
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. 源码路线
PackageSetting的readUserState、modifyUserState和 setter:理解状态 owner。PackageUserStateImpl:查看 bit field、inode、archive 和 change notification。markPackageUninstalledForUserLPw:逐字段核对卸载后的真实值。clearPackageStateForUserLIF:把数据销毁与 inode 失效连接起来。removePackageDataLIF:理解pkg=null、split 保存和 setting 删除条件。Settings.removePackageAndAppIdLPw:跟踪普通 appId 与 shared UID 回收。scheduleWritePackageRestrictions、writeKernelMappingLPr:理解内存、用户文件和内核映射的生效时机。
卸载后的 PackageSetting 不是简单“存在或不存在”:它可以仍是 live package,也可以只保留数据身份,还可能在系统包恢复中被替换为预装设置。用户状态的整组重写、inode、pkg 引用、appId 和持久化文件分别服务不同消费者。逐字段跟踪这些状态,才能可靠判断一次卸载后系统究竟保留了什么,以及重启或重装后为什么会得到当前结果。
