做Android系统定制的朋友应该都遇到过这种情况:需求一句话很简单——把vendor指纹改掉,结果从改mk到最终上板验证,硬生生耗掉一整个下午。我这次碰到的问题是修改ro.vendor.build.fingerprint一直不生效,板子跑起来后getprop看到的还是公版默认值,一开始怀疑是没刷进去,反复整刷了几轮都一样,最后一路从镜像产物查到构建脚本,才发现是踩了“属性生成位置”和“init加载顺序”两个坑。这篇文章就把这次排查的过程完整写下来,包括fingerprint在Android构建系统里是怎么生成的、为什么改了不生效、最终是怎么解决的,以及后续做同类修改时要注意的几个雷区。给正在做RK3566/RK3576这类方案定制,或者被vendor分区属性折腾得头疼的朋友一个参考。
1. 现象复盘:改了这个属性之后,板子稳如泰山
1.1 需求来源与第一版改动
这次项目背景是给一块RK3566方案的行业终端做系统定制,应用层需要根据vendor指纹区分不同的项目和固件版本。需求本身不复杂,一般做法就是在产品mk里定义一个定制值,覆盖默认指纹。项目用的AOSP 12 SDK,设备目录在device/rockchip/rk3566_xxx/下,整体构建体系是RK公版SDK那一套。
第一版改动我选择在device/rockchip/rk3566_xxx/rk3566_xxx.mk里追加快属性:
PRODUCT_VENDOR_PROPERTIES += \ ro.vendor.build.fingerprint=rockchip/rk3566_xxx/rk3566_xxx:12/RKQ1.211119.001/20240522:userdebug/test-keys编译方式也没有特殊处理,直接增量编译后打包刷机。当时预期很简单:vendor/build.prop里出现这个key,刷进vendor分区,系统起来就能读到。
结果刷完机adb shell getprop ro.vendor.build.fingerprint,输出的还是:
rk3566/rk3566_evb/rk3566_evb:12/RKQ1.211119.001/20240522:userdebug/test-keys自定义值完全没有出现。我一开始以为是自己刷机姿势不对,于是重新整刷super分区,又换了一个fastboot脚本,结果一样,板子根本不吃这一套。
1.2 第一反应:是不是没刷进去
遇到属性不生效,我第一反应是“镜像没更新”。Android 10以后vendor分区通常被合并进super分区,如果只刷了boot/system,vendor里那点改动确实会丢失。所以我单独重新编译了vendorimage,确认产物时间戳是新的,然后整包刷入。
刷完再查,还是一样的旧值。此时我心里大概有数了:不是刷机问题,而是构建产物里可能压根没有这个key,或者有key但被系统里另外的值覆盖了。
当时我在板子上顺手试了adb shell setprop ro.vendor.build.fingerprint custom_value,结果自然是失败的,因为ro开头的属性在system_server起来之后不允许二次设置。如果要在运行期验证属性是否能改,直接用setprop去动ro属性没什么意义,更好的方式是查当前生效属性值和它的来源文件。
这块排查我习惯先看三样东西:
adb shell getprop ro.vendor.build.fingerprint,看当前生效值;adb shell cat /vendor/build.prop | grep fingerprint,看vendor文件里到底写了什么;- 上解包工具或者在out目录里直接搜,看最终打进镜像的vendor/build.prop内容。
在板子上执行后发现,/vendor/build.prop里确实有ro.vendor.build.fingerprint,值却是公版默认,不是我定义的那个。这一步基本把问题从“刷机问题”拉回到“构建系统没有按预期生成属性值”。
2. fingerprint在Android构建系统里到底怎么生成的
2.1 build.prop的生成链路
要搞明白为什么不生效,必须先弄清楚ro.vendor.build.fingerprint是从哪个脚本写进build.prop的。AOSP里system和vendor分区的build.prop不是同一个生成入口:
- system/build.prop由
build/make/tools/buildinfo.sh生成; - vendor/build.prop由
build/make/tools/vendor_buildinfo.sh生成; - odm分区的build.prop也有类似的独立生成逻辑。
这两个脚本读取的变量并不完全相同。以AOSP 12为例,system build.prop里ro.build.fingerprint主要来自BUILD_FINGERPRINT,这个值由构建系统根据产品名、设备名、Build ID、Build Number、构建类型拼出来,拼装规则大致是:
$(PRODUCT_BRAND)/$(PRODUCT_NAME)/$(PRODUCT_DEVICE):$(PLATFORM_VERSION)/$(BUILD_ID)/$(BUILD_NUMBER):$(TARGET_BUILD_VARIANT)/$(BUILD_VERSION_TAGS)vendor/build.prop里的ro.vendor.build.fingerprint则对应TARGET_VENDOR_BUILD_FINGERPRINT。这个变量在AOSP里的默认行为是“没有显式声明时就跟随BUILD_FINGERPRINT”,也就是说,如果只改BUILD_FINGERPRINT,正常情况下vendor的值会跟着变;如果只改PRODUCT_VENDOR_PROPERTIES,则是在vendor build.prop的生成阶段追加额外属性。
听上去很清晰,但实际坑就藏在“默认跟随”和“追加属性”之间。
2.2 属性到底写进哪个分区:属性变量的归并表
很多开发在这块吃过亏:以为只要在mk里写了ro.vendor.*前缀,构建系统就会自动把它放进vendor分区。实际上构建系统看的是“你用了哪个属性集变量”,而不是“属性名是什么前缀”。
我用一张表总结常见属性变量和写入位置:
| 变量名 | 写入位置 | 备注 |
|---|---|---|
PRODUCT_PROPERTY_OVERRIDES | system/build.prop | 早期版本常用,新项目尽量少用 |
PRODUCT_SYSTEM_PROPERTIES | system/build.prop | 推荐用于system属性 |
PRODUCT_VENDOR_PROPERTIES | vendor/build.prop | vendor分区属性 |
PRODUCT_ODM_PROPERTIES | odm/etc/build.prop | odm分区属性 |
PRODUCT_DEFAULT_PROPERTY_OVERRIDES | default.prop | 启动早期加载 |
我第一版用的PRODUCT_VENDOR_PROPERTIES确实应该写进vendor/build.prop,理论上方向没错。但问题在于,ro.vendor.build.fingerprint这个key不是普通vendor属性,它是vendor_buildinfo.sh里已经存在的标准输出项,会被构建系统后面的逻辑再次赋值,所以我追加的属性很可能在生成过程中被覆盖掉。
2.3 init上电后属性装载顺序:为什么vendor会被system“抢答”
这一步是整个排查的核心。Android init进程启动时会按固定顺序加载分区里的build.prop,常见顺序大致是:
default.prop/system/build.prop/vendor/build.prop/odm/etc/build.prop- persist属性
关键在于,ro开头的属性有一个“全局只有第一次设置生效”的机制。init先加载system/build.prop,如果里面已经存在ro.vendor.build.fingerprint,这个key就算被注册过了;接着再到vendor/build.prop里遇到同样的key,再想覆盖就会被忽略。
有一个很常见的情况:公版SDK的common.mk或system.prop里用PRODUCT_PROPERTY_OVERRIDES写了ro.vendor.build.fingerprint,把这个理应属于vendor分区的属性塞进了system。结果就是vendor里改得再正确,也敌不过system里的先发制人。我这个项目就是典型,公版平台mk里有一行:
PRODUCT_PROPERTY_OVERRIDES += \ ro.vendor.build.fingerprint=$(BUILD_FINGERPRINT)这一行把ro.vendor.build.fingerprint写进了system/build.prop,后面vendor/build.prop里即使存在自定义值或生成值,init加载时也被system先抢占了。表现在设备上就是你改vendor属性改了个寂寞,getprop永远输出system里那份旧值。
3. 排查过程:从“瞎改”到“看图说话”
3.1 第一板斧:直接grep源码树,看看谁在写这个属性
这种问题最忌讳继续盲目改mk然后反复编译,太浪费时间。正确做法是先看“谁的代码在写这个属性”。
我当时的命令是在源码根目录执行:
grep -rn "ro.vendor.build.fingerprint" \ --include="*.mk" \ --include="*.prop" \ --include="*.sh" \ device/ vendor/ build/结果很快暴露了问题:除了vendor自动生成逻辑外,device/rockchip/common/里确实有页面把ro.vendor.build.fingerprint加进了PRODUCT_PROPERTY_OVERRIDES。也就是说,在构建system/build.prop的时候,这个key就被人为地写进了system分区。
这时候我再回头看自己的PRODUCT_VENDOR_PROPERTIES,就明白了一半。另一半在于,即便我把它正确写进vendor/build.prop,由于init加载顺序中system在前、vendor在后,vendor里的值也不可能生效。
这里补充一个细节:为什么系统属性合并时,公版SDK那行值会“恰好”压过我的定制?因为mk文件的include顺序会影响PRODUCT_PROPERTY_OVERRIDES最终合并顺序,后include的命令在生成build.prop时往往覆盖先include的同名key。我新增的mk在项目目录,平台公共mk在继承链更后面的位置,于是“后写”的公版值反过来覆盖了我“先写”的定制值。
3.2 第二板斧:直接翻产物out目录
源码树里看到影子还不够,要在最终产物里确认。AOSP构建完成后,out/target/product/rk3566_xxx/下会有完整的镜像文件系统目录。我直接查:
grep -n "ro.vendor.build.fingerprint" \ out/target/product/rk3566_xxx/system/build.prop \ out/target/product/rk3566_xxx/vendor/build.prop结果印证了判断:
system/build.prop里有ro.vendor.build.fingerprint,值是公版 SDK 生成的默认指纹;vendor/build.prop里也有ro.vendor.build.fingerprint,值同样是公版默认,我在PRODUCT_VENDOR_PROPERTIES里加的自定义值根本没有出现在产物里。
到这一步,问题已经从“可能没刷进去”转变为“构建时vendor/build.prop里没生成出我的值”。
为什么PRODUCT_VENDOR_PROPERTIES写入失败?再往深一层看,AOSP的vendor build.prop生成逻辑里,ro.vendor.build.fingerprint这类关键属性不是简单从属性集变量里读的,而是直接由vendor_buildinfo.sh按变量生成。如果你没有把值真正传递给TARGET_VENDOR_BUILD_FINGERPRINT,后加的PRODUCT_VENDOR_PROPERTIES会被排序后合并,但未必能覆盖脚本默认输出的同名字段。换句话说,普通PRODUCT_VENDOR_PROPERTIES适合加“新属性”,想改“系统脚本固定输出的属性”就要从源头变量下手。
3.3 第三板斧:make -n看构建命令
为了验证这个判断,我操作了一下构建系统,看vendor build.prop具体由哪条命令生成:
make -n vendorimage | grep -i "buildinfo\|vendor_buildinfo"输出里有类似这样的命令片段:
build/make/tools/vendor_buildinfo.sh \ out/target/product/rk3566_xxx/obj/vendor_build.prop这就明确了:vendor/build.prop的生成入口是vendor_buildinfo.sh,脚本内容就是从TARGET_VENDOR_BUILD_FINGERTIPRINT等变量里取值。所以我后来强制在BoardConfig里设置了自定义值,并同步修改了相关变量,才把vendor里的值真正“焊死”。
3.4 最终定位:三个层面的问题叠加
总结一下,这次排查有以下几个“案发点”:
- 生成层面:
ro.vendor.build.fingerprint不是普通vendor属性,不能单纯靠PRODUCT_VENDOR_PROPERTIES追加覆盖,必须从TARGET_VENDOR_BUILD_FINGERPRINT或构建脚本的取值源头处理; - 产物层面:公版SDK用
PRODUCT_PROPERTY_OVERRIDES把该属性写进了system/build.prop,污染了system分区; - 加载层面:init先加载system/build.prop,再加载vendor/build.prop,system里已有的
ro属性提前占坑,vendor里即便有值也无法覆盖。
任何一个层面单出,都不至于这么折腾。三个问题叠一起,就会呈现“改了不生效、换变量不生效、整刷还不生效”的诡异现象。
4. 修改方案:让vendor指纹真正生效的三步操作
4.1 在正确的变量里写值
第一件事是把vendor指纹的定义放到正确的构建变量。AOSP里系统提供了TARGET_VENDOR_BUILD_FINGERPRINT,在BoardConfig.mk里设置即可:
TARGET_VENDOR_BUILD_FINGERPRINT := rockchip/rk3566_xxx/rk3566_xxx:12/RKQ1.211119.001/20240522:userdebug/test-keys这个变量会被vendor_buildinfo.sh读取,直接写入vendor/build.prop的ro.vendor.build.fingerprint。它比PRODUCT_VENDOR_PROPERTIES更权威,能覆盖默认生成逻辑。
如果你不只是想改vendor,而是想让整个产品的fingerprint统一成定制值,建议在Product.mk里全局修改BUILD_FINGERPRINT,并在BoardConfig里同步TARGET_VENDOR_BUILD_FINGERTIPRINT。这样ro.build.fingerprint和ro.vendor.build.fingerprint就不会互相矛盾。
4.2 清理system里的“抢注”源
光改vendor还不够,system/build.prop里那行公版ro.vendor.build.fingerprint必须处理掉,否则加载顺序决定了vendor里的自定义值永远被遮蔽。
处理方式有两种:
第一种,直接改平台公共mk。将device/rockchip/common/xxx.mk里PRODUCT_PROPERTY_OVERRIDES中的ro.vendor.build.fingerprint相关行删除,或者改成PRODUCT_VENDOR_PROPERTIES。这种方案最干净,但如果你所在项目不允许动公共SDK,就需要第二种。
第二种,项目mk里做过滤。在产品mk里,用filter-out把平台公共mk加进来的同名属性移除:
PRODUCT_PROPERTY_OVERRIDES := \ $(filter-out ro.vendor.build.fingerprint=%,$(PRODUCT_PROPERTY_OVERRIDES))这种方法比较脏,但适合合入责任边界比较严格的团队。需要注意,在mk继承链上执行顺序要对,必须确保平台公共mk先展开,项目mk后过滤。
4.3 完整重编与设备侧清理,避免缓存误判
修复后编译,别急着用增量。我这次吃了增量编译的亏,第一次只改mk不干净,导致产物里老是残存旧值。推荐做法:
rm -rf out/target/product/rk3566_xxx/vendor make -j$(nproc) vendorimage如果你改了system侧属性,还需要:
rm -rf out/target/product/rk3566_xxx/system make -j$(nproc) systemimage最后整包刷入时留意一下,Android 10以上vendor在super里,增量刷机只刷boot/system明显不够,建议直接fastboot flashall或者整包升级。
验证时优先在out目录确认产物:
cat out/target/product/rk3566_xxx/vendor/build.prop | grep fingerprint cat out/target/product/rk3566_xxx/system/build.prop | grep fingerprint产物没问题再上板,避免反复无效刷机。
4.4 一致性检查:别只盯着一个属性
指纹这类属性有一个特点:相互关联。实际产品里和ro.vendor.build.fingerprint一起被读的通常还有:
ro.build.fingerprintro.odm.build.fingerprintro.bootimage.build.fingerprintro.build.version.security_patchro.vendor.build.security_patch
如果只改vendor指纹,不保留其他字段一致,某些安全校验或者系统服务会认为分区之间“指纹不匹配”,甚至可能触发回退逻辑。这次我后续处理时就把BUILD_FINGERPRINT、TARGET_VENDOR_BUILD_FINGERPTIPRINT、TARGET_ODM_BUILD_FINGERPRINT还有TARGET_BOOTIMAGE_BUILD_FINGERPRINT一起梳理了一遍,确保它们指向同一个Build ID和构建类型。
命令行板子上统一验证:
adb shell getprop | grep fingerprint adb shell cat /vendor/build.prop | grep fingerprint adb shell cat /system/build.prop | grep fingerprint三个输出一致,才敢说这次改成功了。
5. 常见问题与避坑清单
5.1 常见坑速查表
这次排查踩了不少坑,整理成速查表,方便以后遇到类似问题直接对照:
| 现象 | 常见原因 | 处理方向 |
|---|---|---|
| 改了mk但out产物没变化 | 增量编译缓存、mk include顺序不对 | 删除对应产物目录,强制重建 |
| system/build.prop里有vendor属性 | 平台mk误用PRODUCT_PROPERTY_OVERRIDES | 过滤或移除该属性定义 |
| vendor里改了但getprop不动 | init先加载system,ro属性被抢注 | 清理system里的同名属性 |
只改BUILD_FINGERPRINT,vendor值没变 | vendor有独立变量控制 | 显式设置TARGET_VENDOR_BUILD_FINGERPRINT |
| 刷完机还是旧值 | super分区里的vendor没更新 | 整包刷入或单独刷vendor到super |
| 改了vendor属性导致部分服务异常 | 指纹族之间不一致 | 同步修改boot/odm/system相关指纹 |
5.2 排查命令清单
再分享几个好用的命令,排查构建属性问题必备:
# 查看设备当前生效的所有指纹 adb shell getprop | grep fingerprint # 查看vendor分区里实际内容 adb shell cat /vendor/build.prop | grep fingerprint # 在源码树中搜索属性写入点 grep -rn "ro.vendor.build.fingerprint" --include="*.mk" --include="*.prop" --include="*.sh" device/ vendor/ build/ # 查看构建命令,确认build.prop生成入口 make -n vendorimage | grep -i "buildinfo\|vendor_buildinfo" # 检查super分区中vendor镜像是否更新 fastboot getvar all | grep -i slot fastboot flash super super.img我在实际排障过程中,sys.system.build.fingerprint这类属性也要顺手看下。Android 10以后有些版本使用带.sys.或.bootimage.前缀的衍生属性,单独排查主属性容易漏掉它们。
5.3 操作心得
这次事件给我最大的一个教训:构建系统里“添加同名属性”和“覆盖已有属性”是两码事。普通属性用PRODUCT_VENDOR_PROPERTIES追加没问题,但ro.vendor.build.fingerprint这种由vendor_buildinfo.sh直接生成的“系统保留属性”,绕开系统变量去硬加就是徒劳。如果你也想让vendor指纹真正生效,建议直接动手设置TARGET_VENDOR_BUILD_FINGERPRINT,这个变量就是它的唯一正确入口。
另外项目里建议加一个产物自检脚本,在构建完成后自动检查out/target/product/xxx/vendor/build.prop和system/build.prop里的指纹是否满足预期。有了这道CI拦路,后面再有人误改属性或者平台SDK偷偷塞属性,一下子就能暴露出来,不用等到刷机才发现。