16、升级包兼容性检测:ro.build.fingerprint校验、vendor与system版本匹配、hwID检测
升级包能不能刷进去,不是看心情的。系统在真正开始升级之前,会做一套严格的「安检」。这套安检如果没过,升级流程直接中止,不会给你任何商量的余地。
说白了,这就是Android系统的自我保护机制。你想想看,如果随便一个包都能刷,那手机变砖的概率得多高?我早期做OTA的时候,就吃过这个亏——一个版本号没对齐的包,刷进去直接导致系统服务起不来,最后只能强刷救砖。
今天我们就来拆解这三道安检:ro.build.fingerprint、vendor与system版本匹配、hwID检测。
16.1 ro.build.fingerprint:系统的「身份证」
先说说这个fingerprint。它是什么?它是系统编译时生成的一个唯一标识字符串。你可以把它理解成系统的身份证号。
在Android里,fingerprint的格式一般是这样的:
ro.build.fingerprint=品牌/产品/设备/Android版本/构建ID/构建类型/构建日期
举个例子:
ro.build.fingerprint=google/pixel6/pixel6:12/SP1A.210812.016/12345678:user/release-keys
这个字符串里包含了:品牌、产品名、设备名、Android版本、构建ID、构建类型、构建日期。每一个字段都是校验的依据。
升级包在编译时,会把自己的fingerprint写进包里的META-INF目录下。设备端在升级前,会拿当前系统的fingerprint和包里的做对比。
校验逻辑:
- 如果包里的fingerprint和当前系统完全一致——说明是同一个版本,不需要升级。
- 如果包里的fingerprint和当前系统不一致——说明版本不同,允许升级。
- 但如果包里的fingerprint比当前系统还旧——那就不行,系统会拒绝降级。
嗯,这里要注意一点:降级保护。Android默认不允许系统降级,因为降级可能导致数据格式不兼容、安全补丁回退等问题。我在项目中遇到过,测试人员想刷回旧版本验证bug,结果死活刷不进去,就是因为这个降级保护机制在起作用。
代码层面,这个校验在RecoverySystem.java里:
// 读取当前系统的fingerprint
String currentFingerprint = SystemProperties.get("ro.build.fingerprint", "");
// 从升级包中读取期望的fingerprint
String packageFingerprint = readFromPackage("META-INF/com/android/metadata");
// 比较
if (!currentFingerprint.equals(packageFingerprint)) {
// 检查是否是降级
if (isDowngrade(currentFingerprint, packageFingerprint)) {
throw new DowngradeException("不允许降级");
}
}
这里有个小技巧:有些厂商会自定义fingerprint的格式,但核心逻辑不变——就是比字符串。我个人习惯在编译脚本里把fingerprint的生成规则写清楚,避免不同分支之间出现混淆。
16.2 vendor与system版本匹配:两个模块的「联姻」
从Android 8.0开始,Google引入了Treble架构。system分区和vendor分区被解耦了。system里放的是Android框架,vendor里放的是硬件相关的驱动和HAL。
这两个分区可以独立升级,但前提是——版本必须匹配。
为什么?因为system里的服务会调用vendor里的HAL接口。如果system升级了,但vendor还是旧版本,接口可能对不上,轻则功能异常,重则系统崩溃。
我记得有一次,我们升级了system里的CameraService,但vendor里的HAL实现还是老版本。结果相机打开就闪退,log里全是接口找不到的错误。后来查了半天,才发现是版本不匹配的问题。
那怎么保证匹配呢?Android引入了VNDK(Vendor Native Development Kit)版本号。每个vendor分区都会声明自己支持的VNDK版本,system分区在升级时会检查这个版本。
具体来说,升级包里的META-INF/com/android/metadata文件会包含:
post-build=google/pixel6/pixel6:12/SP1A.210812.016/12345678:user/release-keys
pre-build=google/pixel6/pixel6:11/RP1A.201105.001/98765432:user/release-keys
post-build-incremental=12345678
pre-build-incremental=98765432
post-sdk-level=32
pre-sdk-level=31
post-security-patch-level=2022-01-05
pre-security-patch-level=2021-10-05
其中post-sdk-level和pre-sdk-level就是用来校验API级别的。如果当前系统的SDK级别是31,但升级包要求32,那说明vendor需要更新。
但vendor能不能更新呢?这取决于升级包的类型:
- 全量包:会同时更新system和vendor,所以版本匹配问题不大。
- 增量包:只更新system,vendor保持不变。这时候就必须保证当前vendor版本和升级包里的system版本是兼容的。
我的建议:在做增量升级时,一定要在升级脚本里显式检查vendor版本。不要依赖默认的兼容性判断。我曾经见过一个案例,vendor版本号没变,但内部HAL接口改了,结果增量升级后一堆功能异常。
16.3 hwID检测:硬件的「指纹」
hwID,全称是Hardware Identifier。它用来标识设备的硬件配置。比如屏幕分辨率、传感器型号、内存大小、存储芯片型号等等。
为什么要检测hwID?因为同一个型号的手机,可能因为供应链不同,用了不同的硬件。比如屏幕,可能这批是三星的,下批是京东方的。如果升级包里的驱动只支持三星屏,刷到京东方屏上,那屏幕就直接黑掉了。
hwID检测的逻辑是这样的:
- 设备在启动时,会读取硬件信息,生成一个hwID。
- 这个hwID会保存在
/proc/device-tree/或者/sys/firmware/devicetree/里。 - 升级包在编译时,会指定支持的hwID列表。
- 升级前,系统对比当前hwID是否在包的支持列表里。
代码层面,这个检测通常在updater二进制里实现:
// 读取当前设备的hwID
std::string current_hwid = read_file("/proc/device-tree/hwid");
// 读取升级包支持的hwID列表
std::vector<std::string> supported_hwids = read_list_from_package("hwid_list.txt");
// 检查
bool hwid_match = false;
for (const auto& hwid : supported_hwids) {
if (current_hwid == hwid) {
hwid_match = true;
break;
}
}
if (!hwid_match) {
error("hwID不匹配,当前设备不支持此升级包");
}
这里有个坑:hwID的生成方式各厂商不同。有的用设备树里的compatible字段,有的用自定义的硬件版本号。我建议在项目初期就把hwID的生成规则定下来,并且写进文档里。不然到了后期,不同批次的产品hwID对不上,升级包就得打好几个版本。
注意事项:hwID检测不能太死板。如果某个硬件只是小改动(比如换了同型号不同批次的传感器),hwID不应该变。否则每次换硬件都要重新发版,维护成本太高。
16.4 三道安检的协作流程
这三道安检不是独立执行的,它们有先后顺序。我画了一张图,帮你理清这个流程:
流程很清晰:先过fingerprint,再过vendor/system匹配,最后过hwID。任何一关没过,升级直接中止。
这里有个细节:增量包和全量包的检测策略不同。全量包因为要刷写所有分区,所以fingerprint校验可以放宽一些——只要不是降级就行。但增量包必须严格校验,因为增量包只更新部分文件,对版本依赖非常强。
16.5 实战中的避坑指南
做了这么多年OTA,我总结了几条经验,分享给你:
- fingerprint的生成要自动化:不要手动拼接,容易出错。用编译脚本自动生成,确保每次构建都有唯一标识。
- vendor版本号要语义化:比如
vendor.vndk.version=30.1.2,主版本号、次版本号、修订号分开。这样兼容性判断更灵活。 - hwID要预留扩展位:比如用32位整数表示,高16位表示硬件平台,低16位表示具体配置。这样换硬件时只需要改低16位。
- 测试要覆盖所有硬件变体:同一个型号的不同批次,hwID可能不同。测试时一定要拿所有变体都测一遍。
一个小技巧:在升级包的metadata里,可以加一个自定义字段,比如required-hwid=0x1234。这样升级脚本可以直接读取这个字段做校验,不用再去解析其他文件。我自己的项目里就是这么做的,维护起来方便很多。
好了,这一章的内容就到这里。兼容性检测是OTA升级的第一道防线,做扎实了,后面的升级流程才能顺利推进。