8. 安装流程(中):StagingManager 与 预安装检查,签名校验与证书链验证

好,咱们接着聊安装流程。上一节我们把 PackageManagerService 的入口和核心调度讲完了,这一节要深入两个非常关键的环节——StagingManager签名校验

说实话,这两个模块在源码里藏得挺深。很多同学看安装流程,看到 PMS 调用 installStage 就以为结束了,其实真正的硬仗才刚刚开始。我个人习惯把 StagingManager 比作「安装现场的工头」,而签名校验就是「入场安检」。这两道关过不了,APK 根本别想进系统。

8.1 StagingManager:安装的幕后调度者

StagingManager 是 Android 10 之后引入的一个关键组件。它的职责说白了就一句话:管理安装包的暂存与提交

你想想看,安装一个 APK 不是直接把文件拷到 /data/app 就完事了。系统需要先做预检查、解析、签名验证、dexopt 等一系列操作。如果这些操作直接在 PMS 的主线程里跑,那 UI 早就卡死了。所以 Google 设计了一个分阶段(staged)安装的机制。

核心流程:

  1. PMS 收到安装请求后,把 APK 交给 StagingManager
  2. StagingManager 创建暂存目录(/data/staging/xxx)
  3. 执行预安装检查(磁盘空间、版本冲突、权限声明等)
  4. 签名校验通过后,提交到正式安装目录

我在项目中遇到过一个问题:某款定制 ROM 在批量预装应用时,StagingManager 的暂存目录写满了,导致后续所有安装都失败。排查了半天才发现是清理策略没触发。嗯,这里要注意——暂存目录的磁盘配额是有限制的,默认只有几百 MB。

8.2 预安装检查:别让问题留到后面

预安装检查是 StagingManager 做的第一件正事。它会在真正写入数据之前,把能查的问题全查一遍。我把它总结为「三查一验」:

检查项 检查内容 常见失败原因
磁盘空间 检查 /data 分区剩余空间是否足够 空间不足、碎片化严重
版本冲突 检查已安装应用的版本号与签名 降级安装、签名不一致
权限声明 检查 APK 声明的权限是否合法 声明了系统级权限但未签名
证书链 验证 APK 签名证书的完整性 自签名证书、证书过期

这里有个细节很多人会忽略:版本冲突检查不只是看版本号大小。系统会对比已安装应用的签名指纹。如果包名相同但签名不同,直接拒绝安装。这是为了防止恶意应用通过同名包替换合法应用。

避坑指南:

我曾经在调试一个系统应用升级问题时,发现预安装检查一直报「INSTALL_FAILED_UPDATE_INCOMPATIBLE」。查了半天,原来是开发同学在测试机上用 debug 签名打了包,而系统里预装的是 release 签名。签名不一致,系统直接拒了。所以调试系统应用时,记得用相同的签名文件。

8.3 签名校验:APK 的身份证

签名校验是 Android 安全体系的基石。没有它,任何人都可以伪造一个「微信」安装包,窃取你的聊天记录。Android 的签名机制经历了三代演变:

  • JAR 签名(v1):基于 ZIP 的签名方式,校验整个 APK 的完整性
  • APK 签名方案 v2:Android 7.0 引入,在 ZIP 中央目录前插入签名块
  • APK 签名方案 v3:Android 9.0 引入,支持密钥轮换

我个人建议新项目直接用 v2 + v3 双签名。v1 方案有个历史遗留问题——它只校验 ZIP 条目,不校验 ZIP 的额外数据区。攻击者可以在额外数据区藏恶意代码而不影响校验结果。

8.4 证书链验证:信任的传递

证书链验证是签名校验中最复杂的一环。系统需要从 APK 的签名证书出发,一路追溯到系统信任的根证书。这个过程我画了一张图:

证书链验证流程 APK 签名证书 颁发者 中间 CA 证书 颁发者 系统根证书(自签名) 验证要点 1. 证书未过期 2. 签名算法安全 3. 证书链完整 4. 根证书受信任 5. 无吊销记录 (Android 不强制 CRL) 验证通过 → 安装继续 | 验证失败 → INSTALL_FAILED_INVALID_APK

这张图展示了典型的证书链结构。APK 签名证书由中间 CA 签发,中间 CA 又由根 CA 签发。系统只信任预置在系统分区里的根证书。如果中间证书过期了或者根证书不在信任列表里,安装就会失败。

注意:Android 的证书链验证和浏览器不太一样。浏览器会检查证书吊销列表(CRL),但 Android 在安装 APK 时默认不检查 CRL。这意味着即使证书被吊销了,只要它在有效期内,系统仍然会认为它是合法的。这是设计上的权衡——为了离线安装场景的兼容性。

8.5 签名校验的代码实现

我们来看看签名校验在源码里是怎么做的。核心实现在 PackageManagerServiceverifySignatures 方法中:

// 伪代码,展示核心逻辑
private boolean verifySignatures(PackageParser.Package pkg) {
    // 1. 获取已安装应用的签名(如果有)
    PackageSignatures existingSigs = getExistingSignatures(pkg.packageName);
    
    // 2. 如果已安装,比较签名是否一致
    if (existingSigs != null) {
        if (!existingSigs.mSignatures.equals(pkg.mSignatures)) {
            return false; // 签名不一致,拒绝安装
        }
    }
    
    // 3. 验证证书链
    for (Signature sig : pkg.mSignatures) {
        if (!verifyCertificateChain(sig)) {
            return false; // 证书链验证失败
        }
    }
    
    return true;
}

private boolean verifyCertificateChain(Signature sig) {
    // 1. 解析 X.509 证书
    // 2. 构建证书链(从叶子到根)
    // 3. 验证每个证书的签名
    // 4. 检查根证书是否在系统信任列表中
    // 5. 检查证书有效期
    return true; // 简化处理
}

这段代码看起来简单,但实际实现要考虑很多边界情况。比如多签名的情况(一个 APK 可以有多个签名),还有 v2/v3 签名方案的兼容性处理。

调试技巧:

如果你在开发中遇到签名相关的问题,可以用 apksigner 工具来验证:

# 验证 APK 签名
apksigner verify --verbose app.apk

# 查看签名详情
apksigner verify --print-certs app.apk

这个工具会告诉你签名方案、证书指纹、证书链是否完整等信息。比看 logcat 直观多了。

8.6 预安装检查的常见失败场景

最后,我总结几个我在实际项目中遇到的预安装检查失败场景,希望能帮你少走弯路:

  • 磁盘空间不足:不是简单的空间不够,有时候是 inode 用完了。/data 分区 inode 耗尽时,df -h 显示还有空间,但就是写不进去文件。
  • 版本降级:系统默认不允许降级安装。除非你在 AndroidManifest 里显式声明 android:allowDowngrade="true",但非系统应用一般没这个权限。
  • 权限升级:如果一个应用之前没申请某个危险权限,新版本突然申请了,系统会弹窗让用户确认。但如果是从普通权限升级到 signature 级别权限,直接拒绝。
  • 证书过期:证书过期后,APK 仍然可以安装(因为签名是在打包时生成的,过期不影响验证)。但系统会记录一条警告日志。不过 Android 14 之后,对过期证书的处理更严格了,可能会直接拒绝安装。

嗯,这一节的内容就到这里。StagingManager 和签名校验是安装流程中最容易出问题的两个环节。理解了它们,你就掌握了 Android 安装安全的核心。


公众号:蓝海资料掘金营,微信deep3321