系统应用卸载
本文承接 系统应用更新、卸载全流程 和 DeletePackageHelper 入口。普通应用卸载通常意味着删除 data APK;系统应用的“卸载”却有两种不同语义:对单个用户只是把包标为未安装,对已更新的系统应用则是删除 /data 覆盖包并重新扫描只读分区的预装 APK。本文只讲 Android 17 中这两条系统包路径,重点解释 DELETE_SYSTEM_APP、disabled system package、用户状态和数据策略如何共同决定结果。
1. 两种语义
1.1 单用户卸载
系统 APK 位于 /system、/product、/vendor 等只读分区,PMS 不会删除这些文件。指定单个用户时,系统包通常保留全局代码,只把该用户的 installed 状态改为 false,并清理该用户数据。其他用户继续使用同一个 APK。
1.2 卸载更新
如果系统包曾被 /data/app 中的更新覆盖,Settings.mDisabledSysPackages 保存了原始系统包。管理员允许的全量卸载会删除 data 更新,启用 disabled 记录,再从原始路径扫描回来。这里的“卸载”本质是回退到预装版本,不是删除系统 APK。
2. 管理员边界
2.1 更新包检查
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
if (PackageManagerServiceUtils.isUpdatedSystemApp(uninstalledPs)
&& ((deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0)) {
final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
if (userInfo == null
|| (!userInfo.isAdmin()
&& !mUserManagerInternal.getUserInfo(
mUserManagerInternal.getProfileParentId(userId)).isAdmin())) {
Slog.w(TAG, "Not removing package " + packageName
+ " as only admin user (or their profile) may downgrade system apps");
return PackageManager.DELETE_FAILED_USER_RESTRICTED;
}
}isUpdatedSystemApp 表示当前包是 data 覆盖版本。没有 DELETE_SYSTEM_APP 内部语义时,普通用户不能通过卸载把系统包降回预装版本;管理员或其 profile parent 才能继续。失败发生在删除动作创建前,原包和用户状态不变。
2.2 内部标志来源
DELETE_SYSTEM_APP 不是用户可以随意设置的公开能力。它由 PMS 的系统包删除路径传递给 deleteInstalledSystemPackage,表示“删除 data 更新并恢复 system package”。如果 disabledPs == null,mayDeletePackageLocked 会拒绝未知系统包,防止把没有可恢复记录的只读包当成普通 APK 删除。
3. 用户状态分支
3.1 执行分支
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
final boolean systemApp = PackageManagerServiceUtils.isSystemApp(ps);
final int userId = user == null ? USER_ALL : user.getIdentifier();
if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0)
&& userId != USER_ALL) {
synchronized (mPm.mLock) {
markPackageUninstalledForUserLPw(ps, user, flags, callingUid);
if (!systemApp) {
clearPackageStateAndReturn =
ps.isInstalledOnAnyOtherUser(allUserHandles, userId)
|| mPm.shouldKeepUninstalledPackageLPr(packageName);
} else {
// 系统包单用户卸载只清理该用户状态
clearPackageStateAndReturn = true;
}
}
if (clearPackageStateAndReturn) {
mRemovePackageHelper.clearPackageStateForUserLIF(
ps, userId, flags);
mPm.scheduleWritePackageRestrictions(user);
return;
}
}系统包单用户路径会直接返回,不进入全量删除。markPackageUninstalledForUserLPw 是 per-user 状态 owner,clearPackageStateForUserLIF 负责该用户数据和状态清理;全局 mPackages、只读 APK 和其他用户不受影响。
3.2 状态保留
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.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 和首次安装时间;完整卸载则重置首次安装时间。系统包单用户卸载因此可能保留更多用户设置,而不是简单把 installed=false 写入后丢弃全部状态。
4. 删除更新
4.1 删除更新
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
private void deleteInstalledSystemPackage(DeletePackageAction action,
int[] allUserHandles, boolean writeSettings) {
int flags = action.mFlags;
final PackageSetting deletedPs = action.mDeletingPs;
final PackageRemovedInfo outInfo = action.mRemovedInfo;
final PackageSetting disabledPs = action.mDisabledPs;
outInfo.mIsRemovedPackageSystemUpdate = true;
if (disabledPs.getVersionCode() < deletedPs.getVersionCode()
|| disabledPs.getAppId() != deletedPs.getAppId()) {
// 预装版本更旧或 appId 改变,不能复用更新包数据
flags &= ~PackageManager.DELETE_KEEP_DATA;
} else {
flags |= PackageManager.DELETE_KEEP_DATA;
}
try (PackageManagerTracedLock installLock =
mPm.mInstallLock.acquireLock()) {
deleteInstalledPackageLIF(deletedPs, USER_ALL, true,
flags, allUserHandles, outInfo, writeSettings);
}
}回退前会比较 disabled 原包与当前 data 包的 versionCode 和 appId。原包版本更低通常意味着数据格式可能不兼容;appId 改变也不能复用旧数据,此时清除 DELETE_KEEP_DATA。只有版本和 appId 兼容时,才把保留数据标志传给底层删除。
4.2 恢复原包
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
@GuardedBy("mPm.mInstallLock")
public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
int[] allUserHandles, boolean writeSettings) throws SystemDeleteException {
final PackageSetting deletedPs = action.mDeletingPs;
final PackageSetting disabledPs = action.mDisabledPs;
synchronized (mPm.mLock) {
// 扫描前必须先解除 disabled 状态
mPm.mSettings.enableSystemPackageLPw(
disabledPs.getPkg().getPackageName());
PackageManagerServiceUtils.removeNativeBinariesLI(deletedPs);
}
try (PackageManagerTracedLock installLock =
mPm.mInstallLock.acquireLock()) {
installPackageFromSystemLIF(disabledPs.getPathString(),
allUserHandles, action.mRemovedInfo.mOrigUsers,
writeSettings);
}
}恢复顺序不能颠倒:先在 mLock 下把原包放回可扫描状态,再在 mInstallLock 下解析和注册 system APK。升级包 native binaries 在此之前删除;否则恢复后的 system 包可能继续加载 data 更新留下的库。
5. 重扫与用户传播
5.1 系统 parse/scan
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
private void installPackageFromSystemLIF(String codePathString,
int[] allUserHandles, int[] origUserHandles,
boolean writeSettings) throws PackageManagerException {
final File codePath = new File(codePathString);
final int parseFlags = mPm.getDefParseFlags()
| ParsingPackageUtils.PARSE_MUST_BE_APK
| ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
final int scanFlags = mPm.getSystemPackageScanFlags(codePath);
final AndroidPackage pkg = initPackageTracedLI(
codePath, parseFlags, scanFlags);
synchronized (mPm.mLock) {
final PackageSetting pkgSetting =
mPm.mSettings.getPackageLPr(pkg.getPackageName());
mSharedLibraries.updateSharedLibraries(pkg, pkgSetting,
null, null, Collections.unmodifiableMap(mPm.mPackages));
}
setPackageInstalledForSystemPackage(pkg, allUserHandles,
origUserHandles, writeSettings);
mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
}恢复不是简单复用 disabled 对象,而是用 PARSE_IS_SYSTEM_DIR 和 system scan flags 重新初始化包,更新 shared library 路径,再准备 app data。这样 Resolver、PermissionManager 和 mPackages 都重新获得 system APK 的运行对象。
5.2 安装集合恢复
for (int userId : allUserHandles) {
final boolean installed = ArrayUtils.contains(origUserHandles, userId);
ps.setInstalled(installed, userId);
if (installed) {
ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
}
}
mPm.mSettings.writeAllUsersPackageRestrictionsLPr();
mPm.mPermissionManager.onPackageInstalled(pkg,
Process.INVALID_UID,
PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
UserHandle.USER_ALL);origUserHandles 来自删除前的用户集合。恢复时只让原先安装过的用户保持 installed=true,曾经卸载过的用户不会被意外重新安装;随后权限服务按 system APK 重新处理安装权限。writeSettings 控制包设置落盘,但用户限制传播本身仍必须持久化。
6. Stub 特例
6.1 压缩 stub
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
final AndroidPackage stubPkg = disabledSystemPs == null
? null : disabledSystemPs.getPkg();
if (stubPkg != null && stubPkg.isStub()) {
if (reEnableStub) {
mPm.enableCompressedPackage(stubPkg, stubPs);
}
}系统 stub 的预装包本身不可运行。恢复流程先扫描并重新建立设置,之后根据删除前用户是否启用该包决定是否解压并重新启用压缩版本;如果所有用户都未安装,则保留 disabled stub,避免无意义地占用 data 空间。
7. 失败与通知
7.1 失败位置
| 阶段 | 失败例子 | 系统包状态 |
|---|---|---|
| 管理员检查 | 非管理员删除更新 | 原 data 包保持运行 |
| 动作构造 | 无 disabled 记录、keyguard 包 | 不改变包表 |
| 删除 data 更新 | native/code 清理失败 | 可能返回内部失败,需查清理日志 |
| 重扫原包 | 路径不存在、解析失败 | 抛出 SystemDeleteException,恢复未完成 |
| 广播/observer | 接收端消失 | 包状态不回滚 |
系统包卸载不是无条件可恢复操作:只读路径损坏、disabled 记录缺失或原 APK 解析失败时,PMS 可能已经删除 data 更新但无法完成恢复。排查时要同时检查 PackageRemovedInfo.mIsRemovedPackageSystemUpdate、disabled 设置和重扫日志。
7.2 广播语义
删除更新会先通过 PackageRemovedInfo 发送移除广播,再完成旧资源最终清理;恢复后还会发送 system package updated 相关通知。广播消费者看到的是一次“更新被移除/系统包重新生效”的事件,不应把它当成普通第三方包永久删除。
8. 验证方法
8.1 单用户卸载
在多用户设备更新一个系统应用,仅对用户 0 执行卸载。断言用户 0 的 installed 状态为 false,用户 10 仍能解析该包,pm path 仍指向同一 data/system 代码路径。该实验验证 per-user 分支,不验证预装恢复。
8.2 卸载更新
以管理员身份执行卸载更新,记录卸载前后的 pm path、dumpsys package 版本和 updated-system 标记。关键断言是路径从 /data/app 回到只读分区,disabled 状态被消费,原先安装用户集合恢复;若版本或 appId 不兼容,用户数据应按源码分支被清除。
8.3 非管理员失败
让普通用户尝试卸载系统更新,预期得到 DELETE_FAILED_USER_RESTRICTED,且 data 更新仍然是当前包。这个输入验证的是入口策略,不应继续检查 restoreDisabledSystemPackageLIF,因为流程尚未到达该函数。
8.4 Stub 恢复
在带 compressed stub 的测试设备上分别验证至少一个用户启用与所有用户未安装两种情况,观察是否调用 enableCompressedPackage。这能证明 stub 的用户状态条件,但不等价于验证 IncFS 或压缩格式本身。
9. 源码路线
DeletePackageHelper.deletePackageVersionedInternal:定位管理员、用户和静默卸载边界。executeDeletePackageLIF:区分单用户状态清理和全量系统包路径。deleteInstalledSystemPackage:理解 versionCode/appId 对DELETE_KEEP_DATA的影响。InstallPackageHelper.restoreDisabledSystemPackageLIF:跟踪启用、native 清理和重扫顺序。installPackageFromSystemLIF:观察 system parse flags、shared libraries 和 app data 准备。setPackageInstalledForSystemPackage:跟踪origUserHandles到 per-user installed 状态。enableCompressedPackage:继续阅读 stub 的解压和重新启用条件。
系统应用卸载的关键是把“用户不可见”与“系统包被移除”分开:单用户操作改变的是用户状态,全量卸载更新改变的是 data 覆盖层;只有在 disabled 记录和原始路径都可用时,PMS 才能重新扫描并恢复预装版本。理解这条分叉,才能正确解释为什么卸载后 APK 文件仍在、为什么某些用户状态不会被重新安装,以及为什么版本或 appId 变化会导致数据被清除。
