安装前检查
本文承接 安装全景、InstallPackageHelper 入口 和 InstallingSession 会话。主题是“包进入 commit 之前,系统究竟检查了什么,以及每项检查由谁负责”。
源码中的“安装前”不是单一函数,而是三层检查:PackageInstallerService 检查调用方能否创建 session;InstallingSession 根据 PackageLite 计算空间和安装位置;InstallPackageHelper.preparePackage() 解析完整包并检查 target SDK、Instant、test-only、签名、替换关系和系统包限制。把这些层混在一起,会误以为所有权限检查都发生在 preparePackage()。
1. 检查分层
1.1 三层入口
1.2 检查与 owner
| 层 | owner | 检查结果写入 |
|---|---|---|
| Session 创建 | PackageInstallerService | 抛 SecurityException/参数异常,session 不产生 |
| 复制前 | InstallingSession | mRet、InstallRequest.returnCode |
| Prepare | InstallPackageHelper | PrepareFailure → request error |
| Reconcile | ReconcilePackageUtils | ReconciledPackage 或 null |
本文到 Prepare 为止;Reconcile 的共享库、签名和 Settings 一致性只在边界处说明,具体专题另行展开。
2. 调用方与 Session
2.1 跨用户和限制
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
int createSessionInternal(SessionParams params, String installerPackageName,
String installerAttributionTag, int callingUid, int userId) throws IOException {
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
throw new SecurityException("User restriction prevents installing");
}
if (params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
!= PackageManager.PERMISSION_GRANTED
&& mContext.checkCallingOrSelfPermission(
Manifest.permission.TEST_MANAGE_ROLLBACKS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("INSTALL_REASON_ROLLBACK requires rollback permission");
}
}跨用户权限、DISALLOW_INSTALL_APPS 和 rollback 权限在 session 创建阶段失败。此时还没有 stage、InstallingSession 或 InstallRequest,所以不能把这些错误归到 preparePackage()。
2.2 Caller 与 flags
if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| PackageInstallerSession.isSystemDataLoaderInstallation(params)
|| PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
// adb installs can override the installingPackageName, but not initiatingPackageName.
installerPackageName = SHELL_PACKAGE_NAME;
} else {
if (callingUid != SYSTEM_UID) {
mAppOps.checkPackage(callingUid, installerPackageName);
}
params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
params.installFlags &= ~PackageManager.INSTALL_ARCHIVED;
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
params.developmentInstallFlags = 0;
}shell/root 被标记为 ADB 安装;普通调用方的 package 归属由 AppOps 验证,并清除不能由其控制的 flags。这里的检查改变的是 session 参数,后续 InstallingSession 读取到的 flags 已经是归一化后的值。
2.3 创建边界
final int sessionId = allocateSessionIdLocked();
final File stageDir = buildSessionDir(sessionId, params);
final PackageInstallerSession session = new PackageInstallerSession(
mInternalCallback, mContext, mPm, mInstallThread.getLooper(),
mStagingManager, sessionId, userId, callingUid, installSource,
params, stageDir, /*isApex=*/ false);
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
writeSessionsAsync();
return sessionId;session 创建只建立可写安装容器。真正的 APK 解析、空间计算和签名检查发生在 commit 后的内部任务阶段。
3. 复制前检查
3.1 最小包信息
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallingSession.java、PackageManagerServiceUtils.java
private void handleStartCopy(InstallRequest request) {
if ((mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
mRet = INSTALL_SUCCEEDED;
return;
}
PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(
mPm.mContext, mPackageLite, mOriginInfo.mResolvedPath,
mInstallFlags, mPackageAbiOverride);APEX 直接跳过普通 APK 的位置路径。普通 APK 使用 PackageLite 生成 PackageInfoLite,这是低成本的包名、版本、split、安装位置和大小信息,不等于完整 ParsedPackage。
3.2 大小与位置
public static PackageInfoLite getMinimalPackageInfo(Context context, PackageLite pkg,
String packagePath, int flags, String abiOverride) {
final PackageInfoLite ret = new PackageInfoLite();
if (packagePath == null || pkg == null) {
Slog.i(TAG, "Invalid package file " + packagePath);
ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
return ret;
}
final File packageFile = new File(packagePath);
final long sizeBytes;
if (!PackageInstallerSession.isArchivedInstallation(flags)) {
try {
sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
} catch (IOException e) {
ret.recommendedInstallLocation = !packageFile.exists()
? InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI
: InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
return ret;
}
} else {
sizeBytes = 0;
}
final PackageInstaller.SessionParams sessionParams =
new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_INVALID);
sessionParams.appPackageName = pkg.getPackageName();
sessionParams.installLocation = pkg.getInstallLocation();
sessionParams.sizeBytes = sizeBytes;
sessionParams.installFlags = flags;
ret.recommendedInstallLocation = InstallLocationUtils.resolveInstallLocation(
context, sessionParams);大小计算失败区分“不存在的 URI”和“存在但不是有效 APK”。归档安装不计算 APK 占用,大小直接为 0。推荐位置由 InstallLocationUtils.resolveInstallLocation() 根据 Manifest installLocation、flags 和 size 决定。
3.3 staged 版本复核
boolean isStaged = (mInstallFlags & INSTALL_STAGED) != 0;
if (isStaged) {
Pair<Integer, String> ret = mPm.verifyReplacingVersionCode(
pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
mRet = ret.first;
if (mRet != INSTALL_SUCCEEDED) {
request.setError(mRet, "Failed to verify version code: " + ret.second);
return;
}
}staged session 在验证和真正安装之间可能跨越较长时间,因此在 handleStartCopy() 又复核 required installed version。普通非 staged 安装不在这里执行这项复核,而是在后续准备/扫描路径中处理更新关系。
3.4 空间恢复
if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation
== InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation(
pkgLite.recommendedInstallLocation, mPackageLite,
mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags);
}
mRet = overrideInstallLocation(pkgLite.packageName,
pkgLite.recommendedInstallLocation, pkgLite.installLocation);只有非 staged 输入会尝试释放 cache;staged 文件已处在确定的目标位置。释放后仍可能返回错误推荐位置,再由 overrideInstallLocation() 转成安装错误码。
4. PreparePackage
4.1 Flags 转换
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
private void preparePackage(InstallRequest request) throws PrepareFailure {
final int[] allUsers = mPm.mUserManager.getUserIds();
final int installFlags = request.getInstallFlags();
final boolean onExternal = request.getVolumeUuid() != null;
final boolean instantApp = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
final boolean fullApp = (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
final boolean virtualPreload =
(installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0;
final boolean isApex = (installFlags & PackageManager.INSTALL_APEX) != 0;
final boolean isRollback =
request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK;
@PackageManagerService.ScanFlags int scanFlags =
SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (request.isInstallMove()) scanFlags |= SCAN_INITIAL;
if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
scanFlags |= SCAN_DONT_KILL_APP;
}
if (instantApp) scanFlags |= SCAN_AS_INSTANT_APP;
if (fullApp) scanFlags |= SCAN_AS_FULL_APP;
if (virtualPreload) scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
if (isApex) scanFlags |= SCAN_AS_APEX;Prepare 不只是验证,它还把 install flags 投影成 scan flags。后续扫描据此知道这是新安装、更新签名、Instant、full app、virtual preload 或 APEX。
4.2 完整解析
final File tmpPackageFile = new File(
isApex ? request.getApexInfo().modulePath : request.getCodePath());
@ParsingPackageUtils.ParseFlags final int parseFlags =
mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
| ParsingPackageUtils.PARSE_ENFORCE_CODE
| (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
final ParsedPackage parsedPackage;
try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
if (!com.android.art.flags.Flags.artManagedInstallFilesValidationApi()) {
AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
}
} catch (PackageParserException e) {
throw new PrepareFailure(e.error,
ExceptionUtils.getCompleteMessage("Failed parse during installPackageLI", e));
}Prepare 使用不读缓存的 preparing parser 完整解析 APK。解析错误在这里从 PackageParserException 转成 PrepareFailure,因此 prepareInstallPackages() 可以把错误写入 InstallRequest。
5. SDK 与特殊包
5.1 最低 target SDK
boolean bypassLowTargetSdkBlock =
(installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0;
if (!bypassLowTargetSdkBlock && parsedPackage.isTestOnly()) {
bypassLowTargetSdkBlock = true;
}
if (!bypassLowTargetSdkBlock
&& parsedPackage.getTargetSdkVersion() < MIN_INSTALLABLE_TARGET_SDK) {
throw new PrepareFailure(INSTALL_FAILED_DEPRECATED_SDK_VERSION,
"App package must target at least SDK version "
+ MIN_INSTALLABLE_TARGET_SDK + ", but found "
+ parsedPackage.getTargetSdkVersion());
}低 target SDK 拦截有两个绕过条件:显式 install flag,或 package 的 testOnly。这是安装安全策略,不是 Manifest 解析阶段的 minSdk 检查;前者决定“能否安装”,后者决定“能否运行”。
5.2 Instant App 限制
if (instantApp) {
if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) {
throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
"Instant app package must target at least O");
}
if (parsedPackage.getSharedUserId() != null) {
throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
"Instant app package may not declare a sharedUserId");
}
}Instant App 要求 target O 以上,且不能声明 sharedUserId。包级 Instant 标志来自 install flags;这不同于前文解析的 visibleToInstantApps 声明。
5.3 Static/test
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (parsedPackage.isStaticSharedLibrary()) {
PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
if (onExternal) {
throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
"Static shared libs can only be installed on internal storage.");
}
}
request.setName(parsedPackage.getPackageName());
if (parsedPackage.isTestOnly()
&& (installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY,
"Failed to install test-only apk. Did you forget to add -t?");
}static shared library 会先改成 PMS 的合成包名,并被限制在内部存储;test-only APK 必须由 install flag 明确允许。两项都发生在完整解析之后、扫描之前。
6. 签名检查
6.1 签名来源
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (request.getSigningDetails() != SigningDetails.UNKNOWN) {
parsedPackage.setSigningDetails(request.getSigningDetails());
} else {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
input, parsedPackage, false /*skipVerify*/);
if (result.isError()) {
throw new PrepareFailure("Failed collect during installPackageLI",
result.getException());
}
parsedPackage.setSigningDetails(result.getResult());
}session 已携带签名时复用它,否则重新收集。签名收集失败会终止 Prepare;成功后 ParsedPackage 才具有可用于更新比较的 SigningDetails。
6.2 Instant 签名
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (instantApp && parsedPackage.getSigningDetails().getSignatureSchemeVersion()
< SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2) {
throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
"Instant app package must be signed with APK Signature Scheme v2 or greater");
}Instant App 额外要求 APK Signature Scheme v2 或更高。这个检查只针对 Instant 安装,不是所有普通 APK 都必须在此处满足同样条件。
6.3 更新签名检查
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (signatureCheckPs != null) {
final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
final SharedUserSetting signatureCheckSus =
mPm.mSettings.getSharedUserSettingLPr(signatureCheckPs);
if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, signatureCheckSus, scanFlags)) {
if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + parsedPackage.getPackageName()
+ " upgrade keys do not match the previously installed version");
}
} else {
final boolean compatMatch = PackageManagerServiceUtils.verifySignatures(
signatureCheckPs, signatureCheckSus, null,
parsedPackage.getSigningDetails(), compareCompat, compareRecover, isRollback);
if (compatMatch) {
ksms.removeAppKeySetDataLPw(parsedPackage.getPackageName());
}
}
}这是更新前的快速签名检查,目的是在重定义权限等更昂贵/更危险操作前尽早失败。真正扫描阶段还会再次进行签名相关处理,因此这里不是“唯一一次签名验证”。
7. 替换与版本
7.1 替换关系
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
String oldName = mPm.mSettings.getRenamedPackageLPr(pkgName);
if (parsedPackage.getOriginalPackages().contains(oldName)
&& mPm.mPackages.containsKey(oldName)) {
parsedPackage.setPackageName(oldName);
pkgName = oldName;
ps = mPm.mSettings.getPackageLPr(oldName);
}
if (ps != null) {
replace = true;
}
}replace flag 触发已安装包查找;如果新包声明了原始包名迁移,PMS 会把当前解析包名切回旧名。后续签名、权限和版本比较使用这个最终 package name。
7.2 权限模型
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (replace) {
final int oldTargetSdk = ps.getTargetSdkVersion();
final int newTargetSdk = parsedPackage.getTargetSdkVersion();
if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
&& newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE,
"Package " + parsedPackage.getPackageName()
+ " new target SDK " + newTargetSdk
+ " doesn't support runtime permissions");
}
if (ps.isPersistent()
&& ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
"Package " + pkgName + " is a persistent app. Persistent apps are not updateable.");
}
}更新不能把已有 runtime permission 模型降回旧模型;persistent app 也不能通过普通非 staged 更新替换。这些是包关系检查,不是 caller 权限检查。
7.3 Required version
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
Pair<Integer, String> verifyReplacingVersionCode(
PackageInfoLite pkgLite, long requiredInstalledVersionCode, int installFlags) {
String packageName = pkgLite.packageName;
synchronized (mPm.mLock) {
PackageSetting dataOwnerPs = mPm.mSettings.getPackageLPr(packageName);
if (dataOwnerPs == null) {
if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
return Pair.create(PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION,
"Required installed version code was "
+ requiredInstalledVersionCode + " but package is not installed");
}
return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
}如果调用方指定 required installed version,而 Settings 中没有包或版本不一致,返回 WRONG_INSTALLED_VERSION。这是 staged 安装在设备状态变化后的再次确认。
7.4 downgrade 规则
if (!PackageManagerServiceUtils.isDowngradePermitted(
installFlags, dataOwnerPs.isDebuggable())) {
try {
PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
} catch (PackageManagerException e) {
String errorMsg = "Downgrade detected: " + e.getMessage();
Slog.w(TAG, errorMsg);
return Pair.create(PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
}
}降级是否允许先由 install flags 和 debug 属性决定;禁止降级时再比较 package long version code、base revision code 和同名 split revision code。系统包即便允许调试降级,在非 debuggable build 上仍会与预加载版本比较。
8. 系统包与特殊约束
8.1 系统包位置
if (systemApp) {
if (onExternal) {
throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
"Cannot install updates to system apps on sdcard");
} else if (instantApp) {
throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
"Cannot update a system app with an instant app");
}
}系统应用更新不能放在外部存储,也不能用 Instant App 形式覆盖。systemApp 来自已有 PackageSetting,不是新包自身 Manifest 的 application 属性。
8.2 app data 一致性
private boolean checkNoAppStorageIsConsistent(AndroidPackage oldPkg,
AndroidPackage newPkg) {
if (oldPkg == null) {
return true;
}
final PackageManager.Property curProp = oldPkg.getProperties().get(
PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
final PackageManager.Property newProp = newPkg.getProperties().get(
PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
if (curProp == null || !curProp.getBoolean()) {
return newProp == null || !newProp.getBoolean();
}
return newProp != null && newProp.getBoolean();
}这个检查在 scan 阶段防止更新改变 no app data storage property。它体现了 PMS046 与 PMS037 的联系:Manifest property 已经进入包模型,但安装前仍要与旧包状态保持一致。
9. 失败传播与清理
9.1 写入 request
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
private boolean prepareInstallPackages(List<InstallRequest> requests) {
for (InstallRequest request : requests) {
try {
request.onPrepareStarted();
preparePackage(request);
} catch (PrepareFailure prepareFailure) {
request.setError(prepareFailure.error, prepareFailure.getMessage());
request.setOriginPackage(prepareFailure.mConflictingPackage);
request.setOriginPermission(prepareFailure.mConflictingPermission);
return false;
} finally {
request.onPrepareFinished();
}
}
return true;
}一个 request 的 PrepareFailure 会停止整批 Prepare,并把冲突 package/permission 一并保存。InstallPackageHelper.installPackagesTraced() 看到 false 后不会 scan/reconcile/commit。
9.2 复制前失败
private void processPendingInstall(InstallRequest installRequest) {
if (mRet == PackageManager.INSTALL_SUCCEEDED) {
mRet = copyApk(installRequest);
}
installRequest.setReturnCode(mRet);
if (mParentInstallingSession != null) {
mParentInstallingSession.tryProcessInstallRequest(installRequest);
} else {
mPm.mHandler.post(() -> processInstallRequests(
mRet == PackageManager.INSTALL_SUCCEEDED,
Collections.singletonList(installRequest)));
}
}位置或 staged 复核失败会让 mRet 非成功,因而跳过 copyApk();普通文件复制失败则由 copyApk() 设置 request 错误。单包直接进入失败收尾,多包先由 parent 汇总。
9.3 失败收尾
private void processApkInstallRequests(boolean success,
List<InstallRequest> installRequests) {
if (!success) {
for (InstallRequest request : installRequests) {
if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
cleanUpForFailedInstall(request);
}
}
for (InstallRequest request : installRequests) {
mPm.restoreAndPostInstall(request);
}
} else {
mPm.installPackagesTraced(installRequests, mMoveInfo);
}
}安装前失败的清理发生在进入主安装管道之前;已经进入 InstallPackageHelper 后的 appId、freezer 和 code path 清理则由 PMS044 所讲的后续收尾负责。
10. 检查时序
10.1 状态图
10.2 检查结果的生效点
| 检查 | 结果何时生效 |
|---|---|
| caller permission/restriction | createSession 立即拒绝 |
| space/location | handleStartCopy 设置 mRet |
| full parse/target SDK | Prepare 抛 PrepareFailure |
| signature compatibility | Prepare 快速拒绝,scan 仍可能再次检查 |
| required version/downgrade | staged 复核或 Prepare/scan 更新检查 |
| system package restriction | Prepare 阶段拒绝 |
| all checks pass | 才允许 copy/scan/reconcile |
11. 测试与诊断
11.1 输入与断言
| 输入 | 断言 | 证明范围 |
|---|---|---|
被 DISALLOW_INSTALL_APPS 用户 | createSession 抛 SecurityException | caller 层 |
| 普通 caller 设置 ADB flag | flag 被清除 | flags 归一化 |
| 无效 stage/package path | PackageInfoLite 推荐位置为 invalid | 复制前检查 |
| 空间不足 | 非 staged 尝试 free cache | 存储恢复 |
| Instant + external volume | session invalid | Prepare 特殊约束 |
| target SDK 过低 | deprecated SDK error | 安全策略 |
| test-only 未带 allow flag | TEST_ONLY error | 开发安装策略 |
| update signature 不匹配 | UPDATE_INCOMPATIBLE | 替换安全 |
| required version 不一致 | WRONG_INSTALLED_VERSION | staged 复核 |
| 禁止 downgrade | VERSION_DOWNGRADE | 版本比较 |
| system app 外部更新 | INVALID_INSTALL_LOCATION | 系统包约束 |
| PrepareFailure | request 写入 error,scan 不执行 | 阶段短路 |
测试应明确检查失败发生在哪一层,并断言 request、stage/code path 和 Settings 是否变化;只看最终安装失败码,无法证明 caller 拒绝、复制前失败和 Prepare 失败之间的差异。
11.2 现场排查顺序
- 先确认 session 是否成功创建,排除 caller/user restriction。
- 查看
InstallingSession.mRet,判断空间、位置或 staged version 复核是否失败。 - 若已进入 Prepare,检查完整解析、target SDK、Instant、test-only 和 static library 分支。
- 对更新包检查
PackageSetting、rename、persistent 和 signature/upgrade-keyset。 - 对 staged 安装检查 required installed version 和 downgrade 结果。
- 最后确认是否生成
ParsedPackage并进入 scan;没有进入 scan 时不要从 resolver 或 dexopt 排查。
12. 源码路线
建议按以下顺序阅读:
PackageInstallerService.createSessionInternal():caller、用户限制和 flags。InstallingSession.handleStartCopy():最小包信息、空间和安装位置。PackageManagerServiceUtils.getMinimalPackageInfo():大小与推荐位置。InstallPackageHelper.preparePackage():完整解析和主要安全约束。ParsingPackageUtils.getSigningDetails():SigningDetails 来源。InstallPackageHelper.verifyReplacingVersionCode():required version 和 downgrade。PackageManagerServiceUtils.checkDowngrade():base/split revision 比较。prepareInstallPackages()和InstallingSession.processPendingInstall():错误写入、短路和清理。
13. 设计收束
安装前检查不是一堵墙,而是三道有明确 owner 的门:
Session 门:调用方能否创建安装容器,flags 是否可信
-> InstallingSession 门:输入是否存在、空间和位置是否可行
-> Prepare 门:包内容、target SDK、Instant、签名、替换和版本是否可接受
-> 才进入 copy/scan/reconcile/commit每道门失败时保存的状态不同:第一道没有 session,第二道只有 mRet/stage 级错误,第三道已经拥有 ParsedPackage 但尚未提交 PMS 全局状态。掌握这些边界,才能在日志中把 SecurityException、INSTALL_FAILED_INSUFFICIENT_STORAGE、PrepareFailure、UPDATE_INCOMPATIBLE 和 VERSION_DOWNGRADE 放回真实的调用位置,而不是把所有问题都归为“安装失败”。
