用户应用卸载
本文承接 卸载全流程 和 DeletePackageHelper 入口,只研究非系统包的真实删除路径。系统应用的恢复语义见 系统应用卸载。本文回答:单用户卸载为何可能只改状态,DELETE_KEEP_DATA 保留什么,以及最后一个用户卸载时谁删除包设置、权限和 code path。
1. 删除分叉
第三方 APK 位于 /data/app。单用户请求先把该用户设为未安装;如果其他用户仍安装,或 PMS 要保留未安装包,则 APK 和全局设置继续存在。只有全用户删除或已无其他用户使用时,才进入完整删除。
2. 单用户状态
2.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;
}
}PackageSetting 持有 per-user installed 状态。第三方包单用户卸载时,markPackageUninstalledForUserLPw 先写状态;只有确认没有其他用户需要该 APK 且不需要保留包设置,才继续全量删除。调用方看到成功,只能说明指定用户不再安装,不能推出 APK 已经消失。
2.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());
} else {
enabledComponents = null;
disabledComponents = null;
}
ps.setUserState(nextUserId, 0, 0, false, false, false,
false, 0, null, COMPONENT_ENABLED_STATE_DEFAULT,
enabledComponents, disabledComponents,
INSTALL_REASON_UNKNOWN, UNINSTALL_REASON_UNKNOWN,
null, null, 0, USER_MIN_ASPECT_RATIO_UNSET,
archiveState, false, USER_MIN_ASPECT_RATIO_UNSET,
PERSONAL_CONTEXT_MODE_UNSET);Android 17 在保留数据时还保留组件启用集合和 archive state;完整卸载则清除这些用户设置。源码签名包含更多 per-user 字段,阅读时应以当前分支为准,不要套用旧版本 setUserState 参数表。
3. 全量删除
3.1 记录删除信息
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
private void deleteInstalledPackageLIF(PackageSetting ps,
int userId, boolean deleteCodeAndResources, int flags,
int[] allUserHandles, PackageRemovedInfo outInfo,
boolean writeSettings) {
synchronized (mPm.mLock) {
outInfo.mUid = ps.getAppId();
outInfo.mBroadcastAllowList =
mPm.mAppsFilter.getVisibilityAllowList(
mPm.snapshotComputer(), ps, allUserHandles,
mPm.mSettings.getPackagesLocked());
}
mRemovePackageHelper.removePackageDataLIF(
ps, userId, allUserHandles, outInfo, flags, writeSettings);
if (deleteCodeAndResources) {
outInfo.mArgs = new CleanUpArgs(
ps.getName(), ps.getPathString());
}
}PackageRemovedInfo 在包状态被改写前保存 appId、广播可见性和 code path。RemovePackageHelper 先清理内部状态,再由 CleanUpArgs 在合适时机删除 APK 和资源;状态删除与物理文件删除不是同一个调用。
3.2 内部结构移除
源码文件:frameworks/base/services/core/java/com/android/server/pm/RemovePackageHelper.java
final boolean shouldDeletePackageSetting =
shouldDeletePackageSetting(deletedPs, targetUserId,
allUserHandles, flags);
clearPackageStateForUserLIF(deletedPs,
shouldDeletePackageSetting ? UserHandle.USER_ALL : targetUserId,
flags);
removePackageLI(packageName, (flags & DELETE_CHATTY) != 0);
if (!deletedPs.isSystem()) {
// 保留 PackageSetting 时也不能继续使用旧 AndroidPackage
deletedPs.setPkg(null);
}
if (shouldDeletePackageSetting) {
synchronized (mPm.mLock) {
outInfo.mIsAppIdRemoved =
mPm.mSettings.removePackageAndAppIdLPw(packageName);
if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
mPermissionManager.onPackageUninstalled(
packageName, deletedPs.getAppId(), deletedPs,
deletedPkg, sharedUserPkgs, UserHandle.USER_ALL);
}
mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName());
}
}DELETE_KEEP_DATA 通常使 shouldDeletePackageSetting 为 false:包设置保留,但非系统包的 pkg 指针被置空,防止查询继续把它当作运行包。完全删除时才移除 appId、权限卸载记录和 renamed package。
3.3 数据 flags
源码文件:frameworks/base/services/core/java/com/android/server/pm/RemovePackageHelper.java
int appDataDeletionFlags = FLAG_STORAGE_DE | FLAG_STORAGE_CE
| FLAG_STORAGE_EXTERNAL;
if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
if ((flags & PackageManager.DELETE_ARCHIVE) != 0) {
mAppDataHelper.clearAppDataLIF(resolvedPkg, userId,
appDataDeletionFlags | Installer.FLAG_CLEAR_CACHE_ONLY);
mAppDataHelper.clearAppDataLIF(resolvedPkg, userId,
appDataDeletionFlags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
return;
}
mAppDataHelper.destroyAppDataLIF(resolvedPkg, userId,
appDataDeletionFlags);没有 DELETE_KEEP_DATA 时销毁 CE、DE 和 external data;保留数据时不销毁个人数据,archive 模式只清理 cache 与 code cache。ART profile 默认另行清理,除非 flags 要求保留。
4. 特殊条件
4.1 增量包
源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java
if (ps.isIncremental()) {
// 增量 APK 的 ART 产物可能不在 APK 目录内
deleteArtDexoptArtifacts(packageName);
}
deleteInstalledPackageLIF(ps, userId, deleteCodeAndResources,
flags, allUserHandles, outInfo, writeSettings);增量包必须显式清理外部 dexopt 产物,否则删除 APK 目录并不足以回收所有编译结果。
4.2 Shared library
删除前会查询 static shared library 或 SDK library 的使用者。存在必需客户端时拒绝删除;SDK library independence 只在所有客户端声明 optional 时可能放行。这个判断发生在包状态被移除前。
4.3 Profile 级联
单用户删除成功后,父用户的 child profile 只有在 deleteAppWithParent 为 true 且包已安装时才递归删除。child 删除失败会返回 DELETE_FAILED_FOR_CHILD_PROFILE,因此父用户成功不代表 profile 树全部完成。
5. 验证方法
5.1 多用户
用户 0 和用户 10 都安装同一第三方 APK,仅对用户 0 卸载。断言用户 0 的 installed/data 状态被清理,用户 10 仍能解析该包且 pm path 仍存在;随后全用户卸载,确认包设置、权限和 code path 消失。
5.2 保留数据
执行 pm uninstall -k,再安装同包名 APK。检查保留的 version/split 元数据、组件状态和数据目录;archive 模式还应清理 cache/code-cache。该实验只证明 PMS 保留了数据,不证明旧代码一定兼容新数据。
5.3 依赖与增量
卸载被其他包依赖的 shared library host,预期收到依赖冲突;卸载增量包,检查 ART dexopt 产物也被清理。两组实验分别覆盖前置拒绝和额外资源路径。
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'installed=|versionCode|codePath'6. 源码路线
deletePackageVersionedInternal:入口用户范围和策略。deletePackageX:冻结、版本和删除结果。executeDeletePackageLIF:单用户与全量分叉。deleteInstalledPackageLIF:删除信息和 code path。RemovePackageHelper.removePackageDataLIF:设置、权限和包对象移除。clearPackageStateForUserLIF:数据、cache、ART profile 和 archive。- 增量、shared library、profile 级联:覆盖非正常分支。
用户应用卸载不是一个“删目录”函数:PackageSetting 管理用户状态,RemovePackageHelper 管理运行时结构与数据,CleanUpArgs 延迟处理 code path,PermissionManager 和 BroadcastHelper 消费删除结果。把这些对象分开,才能准确判断一次卸载到底只影响某个用户,还是已经让应用从整个设备消失。
