Skip to content

安装前检查

追踪 Android 17 安装前的调用方限制、存储位置、target SDK、签名、版本和系统包检查。

基于android-17.0.0_r1
AndroidPackageManagerServiceInstallPackageHelperPackageInstaller签名验证源码阅读

安装前检查 ​

本文承接 安装全景、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 不产生
复制前InstallingSessionmRet、InstallRequest.returnCode
PrepareInstallPackageHelperPrepareFailure → request error
ReconcileReconcilePackageUtilsReconciledPackage 或 null

本文到 Prepare 为止;Reconcile 的共享库、签名和 Settings 一致性只在边界处说明,具体专题另行展开。

2. 调用方与 Session ​

2.1 跨用户和限制 ​

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

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 ​

java
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 创建边界 ​

java
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

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 大小与位置 ​

java
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 版本复核 ​

java
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 空间恢复 ​

java
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

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 完整解析 ​

java
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 ​

java
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 限制 ​

java
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

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

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

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

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 替换关系 ​

java
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

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

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 规则 ​

java
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 系统包位置 ​

java
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 一致性 ​

java
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

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 复制前失败 ​

java
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 失败收尾 ​

java
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/restrictioncreateSession 立即拒绝
space/locationhandleStartCopy 设置 mRet
full parse/target SDKPrepare 抛 PrepareFailure
signature compatibilityPrepare 快速拒绝,scan 仍可能再次检查
required version/downgradestaged 复核或 Prepare/scan 更新检查
system package restrictionPrepare 阶段拒绝
all checks pass才允许 copy/scan/reconcile

11. 测试与诊断 ​

11.1 输入与断言 ​

输入断言证明范围
被 DISALLOW_INSTALL_APPS 用户createSession 抛 SecurityExceptioncaller 层
普通 caller 设置 ADB flagflag 被清除flags 归一化
无效 stage/package pathPackageInfoLite 推荐位置为 invalid复制前检查
空间不足非 staged 尝试 free cache存储恢复
Instant + external volumesession invalidPrepare 特殊约束
target SDK 过低deprecated SDK error安全策略
test-only 未带 allow flagTEST_ONLY error开发安装策略
update signature 不匹配UPDATE_INCOMPATIBLE替换安全
required version 不一致WRONG_INSTALLED_VERSIONstaged 复核
禁止 downgradeVERSION_DOWNGRADE版本比较
system app 外部更新INVALID_INSTALL_LOCATION系统包约束
PrepareFailurerequest 写入 error,scan 不执行阶段短路

测试应明确检查失败发生在哪一层,并断言 request、stage/code path 和 Settings 是否变化;只看最终安装失败码,无法证明 caller 拒绝、复制前失败和 Prepare 失败之间的差异。

11.2 现场排查顺序 ​

  1. 先确认 session 是否成功创建,排除 caller/user restriction。
  2. 查看 InstallingSession.mRet,判断空间、位置或 staged version 复核是否失败。
  3. 若已进入 Prepare,检查完整解析、target SDK、Instant、test-only 和 static library 分支。
  4. 对更新包检查 PackageSetting、rename、persistent 和 signature/upgrade-keyset。
  5. 对 staged 安装检查 required installed version 和 downgrade 结果。
  6. 最后确认是否生成 ParsedPackage 并进入 scan;没有进入 scan 时不要从 resolver 或 dexopt 排查。

12. 源码路线 ​

建议按以下顺序阅读:

  1. PackageInstallerService.createSessionInternal():caller、用户限制和 flags。
  2. InstallingSession.handleStartCopy():最小包信息、空间和安装位置。
  3. PackageManagerServiceUtils.getMinimalPackageInfo():大小与推荐位置。
  4. InstallPackageHelper.preparePackage():完整解析和主要安全约束。
  5. ParsingPackageUtils.getSigningDetails():SigningDetails 来源。
  6. InstallPackageHelper.verifyReplacingVersionCode():required version 和 downgrade。
  7. PackageManagerServiceUtils.checkDowngrade():base/split revision 比较。
  8. prepareInstallPackages() 和 InstallingSession.processPendingInstall():错误写入、短路和清理。

13. 设计收束 ​

安装前检查不是一堵墙,而是三道有明确 owner 的门:

text
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 放回真实的调用位置,而不是把所有问题都归为“安装失败”。