8. 安装流程(中):StagingManager 与 预安装检查,签名校验与证书链验证
好,咱们接着聊安装流程。上一节我们把 PackageManagerService 的入口和核心调度讲完了,这一节要深入两个非常关键的环节——StagingManager 和 签名校验。
说实话,这两个模块在源码里藏得挺深。很多同学看安装流程,看到 PMS 调用 installStage 就以为结束了,其实真正的硬仗才刚刚开始。我个人习惯把 StagingManager 比作「安装现场的工头」,而签名校验就是「入场安检」。这两道关过不了,APK 根本别想进系统。
8.1 StagingManager:安装的幕后调度者
StagingManager 是 Android 10 之后引入的一个关键组件。它的职责说白了就一句话:管理安装包的暂存与提交。
你想想看,安装一个 APK 不是直接把文件拷到 /data/app 就完事了。系统需要先做预检查、解析、签名验证、dexopt 等一系列操作。如果这些操作直接在 PMS 的主线程里跑,那 UI 早就卡死了。所以 Google 设计了一个分阶段(staged)安装的机制。
核心流程:
- PMS 收到安装请求后,把 APK 交给 StagingManager
- StagingManager 创建暂存目录(/data/staging/xxx)
- 执行预安装检查(磁盘空间、版本冲突、权限声明等)
- 签名校验通过后,提交到正式安装目录
我在项目中遇到过一个问题:某款定制 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 签发,中间 CA 又由根 CA 签发。系统只信任预置在系统分区里的根证书。如果中间证书过期了或者根证书不在信任列表里,安装就会失败。
注意:Android 的证书链验证和浏览器不太一样。浏览器会检查证书吊销列表(CRL),但 Android 在安装 APK 时默认不检查 CRL。这意味着即使证书被吊销了,只要它在有效期内,系统仍然会认为它是合法的。这是设计上的权衡——为了离线安装场景的兼容性。
8.5 签名校验的代码实现
我们来看看签名校验在源码里是怎么做的。核心实现在 PackageManagerService 的 verifySignatures 方法中:
// 伪代码,展示核心逻辑
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 安装安全的核心。