Skip to content

安装失败清理

追踪 Android 17 安装失败后的代码路径、内存状态、用户数据和缓存清理边界。

基于android-17.0.0_r1
AndroidPMSRemovePackageHelperInstallPackageHelperFailureRecovery

安装失败清理 ​

本文面向已经读过 安装全景、安装后注册 和 压缩系统 Stub 包 的读者。本文只讨论安装失败后的撤销与清理,不重复成功安装、卸载策略或 ART 编译流程。核心问题是:解析失败、扫描失败、移动失败或包移动失败时,PMS 怎样知道应该删哪条 code path,是否要删 app data,以及为什么一个失败安装可能仍保留 PackageSetting。

1. 清理责任 ​

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

InstallPackageHelper 决定何时失败;RemovePackageHelper 执行代码目录、app data、权限和内部 package object 的清理。失败安装通常只删除本次产生的临时 code path,不能套用完整卸载的“删除所有用户数据”。

2. 扫描失败 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java;符号:扫描结果处理分支。

当 data app 解析或扫描失败,PMS 会记录错误并调用 mRemovePackageHelper.removeCodePath(result.scanFile):

java
if ((scanParams.scanFlags & SCAN_AS_SYSTEM) == 0
        && errorCode != PackageManager.INSTALL_SUCCEEDED) {
    logCriticalInfo(Log.WARN,
            "Deleting invalid package at " + result.scanFile);
    mRemovePackageHelper.removeCodePath(result.scanFile);
}

system 分区包不走同一删除分支,因为 system code path 不能被当作 data 临时安装目录删除;APEX-in-APEX 失败还会通知 ApexManager.reportErrorWithApkInApex。因此“扫描失败后文件还在”要先确认路径属于 system、data 还是 APEX。

3. removeCodePath ​

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

removeCodePath 在 mInstallLock 下进入 removeCodePathLI。目录路径由 Installer 删除;incremental path 还要通过 IncrementalManager.rmPackageDir 删除对应 package directory。若目录父级是随机目录,父目录也会被删除并清理 PackageCacher 缓存。

java
if (isIncremental) {
    mIncrementalManager.rmPackageDir(
            needRemoveParent ? codePathParent : codePath);
}
mInstaller.rmPackageDir(packageName, codePath.getAbsolutePath());
if (needRemoveParent) {
    mInstaller.rmPackageDir(packageName,
            codePathParent.getAbsolutePath());
    removeCachedResult(codePathParent);
}

Installer 异常只记录 warning,不向上重新抛出。也就是说安装可以已经报告失败,但磁盘残留需要下一次 cleanup 或人工诊断;不能把 removeCodePath 返回无异常当成“目录一定删除”。

4. 安装后失败 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java;符号:doPostInstallCleanUp。

安装流程完成主事务后,doPostInstallCleanUp 按 request return code 决定清理:

java
if (moveInfo != null) {
    if (request.getReturnCode() == INSTALL_SUCCEEDED) {
        mRemovePackageHelper.cleanUpForMoveInstall(
                moveInfo.mFromUuid, moveInfo.mPackageName,
                moveInfo.mFromCodePath);
    } else {
        mRemovePackageHelper.cleanUpForMoveInstall(
                moveInfo.mToUuid, moveInfo.mPackageName,
                moveInfo.mFromCodePath);
    }
} else if (request.getReturnCode() != INSTALL_SUCCEEDED) {
    mRemovePackageHelper.removeCodePath(request.getCodeFile());
}

move install 失败时清理目标卷;普通安装失败时清理 request 的 code file。app data cleanup 由更具体的失败阶段决定,不是这里无条件销毁。

5. 内存对象清理 ​

成功扫描并注册过的包,失败回滚需要从 mPackages、ComponentResolver、PermissionManager、PackageProperty、Instrumentation 和 shared library 表中移除。removePackageLI 在 mInstallLock 下进入 mLock,调用 cleanPackageDataStructuresLILPw 执行这些消费者的反向更新。

java
synchronized (mPm.mLock) {
    final AndroidPackage removedPackage =
            mPm.mPackages.remove(packageName);
    if (removedPackage != null) {
        cleanPackageDataStructuresLILPw(
                removedPackage,
                AndroidPackageLegacyUtils.isSystem(removedPackage),
                chatty);
    }
}

移除 mPackages 不等于移除 Settings。removePackageDataLIF 还要根据 DELETE_KEEP_DATA、其他 user 数据和 system disabled package 判断是否删除 PackageSetting;失败安装可能保留 setting 以支持后续恢复或清理。

6. 用户数据分支 ​

clearPackageStateForUserLIF 首先清 ART profiles,再决定是否销毁 app data。DELETE_KEEP_DATA 会保留个人数据;archive 例外只清 cache/code-cache。没有 KEEP_DATA 时才调用 AppDataHelper.destroyAppDataLIF,并清除 CE/DE/PCC inode。

源码文件:frameworks/base/services/core/java/com/android/server/pm/RemovePackageHelper.java;符号:clearPackageStateForUserLIF。

java
if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
    if ((flags & PackageManager.DELETE_ARCHIVE) != 0) {
        mAppDataHelper.clearAppDataLIF(... FLAG_CLEAR_CACHE_ONLY);
        mAppDataHelper.clearAppDataLIF(... FLAG_CLEAR_CODE_CACHE_ONLY);
    }
    return;
}

这解释了 partial install cleanup 的要求:调用方必须传入正确 KEEP_DATA 语义,否则“清理失败安装”可能误删本应保留的数据,或留下应该删除的 app data。

7. shared UID 与库 ​

清理内部 package object 时,system 包持有的 shared libraries、SDK library 和 static shared library 会从 SharedLibrariesImpl 移除。完全删除 PackageSetting 后,PermissionManager 还要处理 shared user;如果 shared UID 仍有其他成员,appId 和权限状态不能一起删除。

因此失败回滚不能只删除 APK 文件:下次安装同包名/同 shared UID 时,残留 library 或权限状态会改变 reconcile 结果。

8. Move install ​

cleanUpForMoveInstall 在 mInstallLock 下销毁目标卷上的 DE/CE 数据和 code path,但显式保留 ART profiles,因为 move 只改变存储位置,删除唯一 profile 副本会破坏后续优化。成功移动清理旧卷;失败移动清理目标卷。

java
final int flags = FLAG_STORAGE_DE | FLAG_STORAGE_CE
        | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES;
for (int userId : userIds) {
    mPm.mInstaller.destroyAppData(volumeUuid,
            packageName, userId, flags, 0, 0);
}
removeCodePathLI(codeFile);

移动失败的 cleanup 目标不是“原包全部删除”,而是回收新卷上已经创建的半成品,同时保留原卷和 profiles 的可恢复性。

9. 锁与顺序 ​

RemovePackageHelper 的代码删除和数据清理要求 mInstallLock;内部 mPackages、Settings、resolver 和权限表要求 mLock。Android 17 的典型顺序是先 install lock,再进入 PMS lock;反向获取可能造成安装/删除死锁。

失败清理是可重复调用的:路径不存在时 removeCodePathLI 直接返回;但内部状态和磁盘状态可能只完成一部分,后续 cleanup 必须分别验证。

10. 失败定位 ​

现象检查点解释
失败 APK 仍在 data/appremoveCodePath 日志、Installer 异常清理失败会记录 warning,不一定抛异常
mPackages 无包但 Settings 有 settingremovePackageDataLIF / KEEP_DATA内存对象和 PackageSetting 生命周期不同
shared UID 下次安装异常library/permission cleanup共享成员存在时不能删除整个 appId
move 失败后原包不可恢复cleanUpForMoveInstall 卷参数失败时应清目标卷,不应删原卷
stub 失败后 system 包不可启动stub restore/disable 分支stub 必须恢复但最终保持 disabled
失败安装误删用户数据DELETE_KEEP_DATA/ARCHIVE flags数据清理范围由 flags 决定

11. 源码练习 ​

  1. 从 data app 扫描失败分支追到 removeCodePath,说明为什么 system path、data path 和 APK-in-APEX 的处理不同。
  2. 构造 move install 失败,判断 cleanUpForMoveInstall 的 volume、profile flag 和 code path 目标。
  3. 对比 removePackageLI、removePackageDataLIF 和 removeCodePathLI,列出各自删除的对象以及不会删除的对象。
  4. 给定失败安装后 mPackages 已清空但 PackageSetting 仍存在,沿 KEEP_DATA、shared UID、其他 user data 三个条件判断是否符合 Android 17 的设计。