22、系统属性与配置:build.prop与system.prop管理,ro.与persist.属性的兼容性

系统属性,说白了就是Android系统的“全局变量”。

我刚开始做系统开发时,觉得这东西不就是个键值对吗?改个值能有多复杂?直到有一次,我把一个ro.开头的属性写错了地方,结果整批设备OTA后全部变砖……嗯,从那以后,我再也不敢小看这两行配置文件了。

22.1 build.prop 与 system.prop 的区别

先搞清楚这两个文件分别管什么。

文件 路径 加载时机 主要用途
build.prop /system/build.prop 系统启动时,init进程加载 设备标识、版本号、编译信息
system.prop /system/system.prop 系统服务启动后,property_service加载 厂商自定义属性、功能开关

我个人习惯把build.prop看作“身份证”,里面放的是ro.build.version.sdkro.product.model这类只读信息。而system.prop更像“功能配置表”,比如persist.sys.usb.config这种需要持久化的开关。

关键点:build.prop 在 init 阶段就加载了,system.prop 要等到 property_service 启动后才生效。如果你在 system.prop 里放了一个 ro. 属性,而某个 native 服务在 init 阶段就要读它——那它读到的会是空值。

22.2 ro. 属性:一次写入,终身只读

ro. 前缀代表 read-only。一旦设置,整个系统运行期间就不能改了。

# build.prop 示例
ro.product.model=Pixel 6
ro.build.version.sdk=33
ro.hardware=gs101

为什么设计成只读?你想想看,如果某个系统服务启动时读了ro.build.version.sdk,然后另一个进程偷偷把它改了——整个系统的行为就乱套了。

我在项目中遇到过一个问题:某款平板在OTA升级后,ro.build.fingerprint没有更新。结果Google Play认为设备指纹没变,拒绝推送新版本的GMS认证。排查了半天,发现是编译脚本里把fingerprint写死了,没有用$(BUILD_FINGERPRINT)变量。

避坑指南:我曾经在 system.prop 里写了一个 ro. 属性,以为重启后就能生效。结果发现 property_service 加载 system.prop 时,ro. 属性已经被锁定了。正确的做法是:所有 ro. 属性必须放在 build.prop 里,或者通过 setprop ro.xxx yyy 在 init.rc 中早期设置。

22.3 persist. 属性:重启不丢失的秘密

persist. 前缀的属性,会在写入后保存到 /data/property/ 目录下。即使重启,值也不会丢。

# 设置 persist 属性
adb shell setprop persist.sys.timezone Asia/Shanghai
adb shell setprop persist.sys.usb.config mtp,adb

它的实现原理其实很简单:property_service 在收到 setprop persist.xxx yyy 时,会同步写一份到 /data/property/persistent_properties 文件。下次启动时,init 进程会从这个文件里恢复所有 persist. 属性。

但这里有个坑——/data 分区如果被格式化,所有 persist. 属性就全没了。我记得有一次做双系统切换,A系统写了一个 persist.sys.switch_count,结果切到B系统后 /data 被重新挂载,计数器直接归零……

我的建议:如果某个配置真的需要跨分区、跨系统保留,别用 persist. 属性。考虑用 vbmeta 或者单独的分区来存。persist. 只适合那些“丢了也无所谓,最多恢复出厂设置”的场景。

22.4 兼容性:升级时最容易翻车的地方

系统升级时,build.prop 和 system.prop 会被新版本覆盖。但 persist. 属性不会——它留在 /data 里。

这就带来了兼容性问题。举个例子:

  • Android 12 里,persist.sys.fast_charge 的值是 1
  • Android 13 把这个属性废弃了,改成了 persist.sys.charging.speed
  • OTA 升级后,旧属性还在,但新系统根本不读它

结果就是:用户觉得充电变慢了,但实际上只是系统没读到正确的配置。

我处理过一个更隐蔽的问题:某厂商在 Android 11 里用 persist.vendor.camera.hal 控制相机 HAL 版本。升级到 Android 12 后,新系统默认用 ro.vendor.camera.hal 来判断。但 persist. 属性优先级高于 ro. 属性,导致系统一直用旧的 HAL 版本,新功能死活不生效。

兼容性检查清单:

  1. 升级前,扫描所有 persist. 属性,确认新系统是否还支持
  2. 如果废弃了某个 persist. 属性,在升级脚本里主动删除它
  3. 新引入的 persist. 属性,要设置一个合理的默认值
  4. ro. 属性如果改了名字,旧版 build.prop 里的值不会自动迁移

22.5 实战:如何安全地管理系统属性

下面是我总结的一套操作流程,你可以直接拿去用。

22.5.1 查看当前所有属性

# 查看所有属性
adb shell getprop

# 只查看 ro. 属性
adb shell getprop | grep "^\[ro\."

# 只查看 persist. 属性
adb shell getprop | grep "^\[persist\."

22.5.2 在编译时添加自定义属性

如果你在 AOSP 源码里编译,可以在 device/<vendor>/<product>/device.mk 中添加:

# 添加到 build.prop
PRODUCT_PROPERTY_OVERRIDES += \
    ro.vendor.xyz.feature=true \
    ro.vendor.xyz.version=2.0

# 添加到 system.prop
PRODUCT_SYSTEM_PROPERTIES += \
    persist.vendor.xyz.timeout=5000 \
    vendor.xyz.debug=false

22.5.3 OTA 升级时处理属性迁移

META-INF/com/google/android/updater-script 中,可以这样处理:

# 删除废弃的 persist 属性
delete_recursive("/data/property/persistent_properties");

# 或者用 set_metadata 直接修改
setprop("persist.sys.old_feature", "");
setprop("persist.sys.new_feature", "enabled");

注意:不要在 updater-script 里直接 setprop 太多属性。recovery 模式下的 property_service 功能有限,大量 setprop 可能导致升级失败。我一般只处理关键的 3-5 个属性,其余的交给首次启动时的 init.rc 脚本。

22.6 一张图理清属性管理全流程

系统属性管理全流程 编译阶段 build.prop / system.prop ro. 属性在此写入 启动阶段 init 加载 build.prop property_service 加载 system.prop 运行阶段 getprop / setprop persist. 写入 /data 持久化存储 /data/property/persistent_properties setprop persist. 重启恢复 兼容性风险 OTA 后 persist. 残留 ro. 属性:编译时写入,运行时只读 persist. 属性:运行时写入,重启后保留 普通属性:运行时写入,重启后丢失 编译 启动 运行 持久化 风险

22.7 总结几个实用技巧

  • 调试时临时改属性:adb shell setprop 就行,重启就没了。别手滑写到 build.prop 里。
  • 想永久改一个属性:如果是 ro. 属性,必须重新编译刷机。如果是 persist. 属性,setprop 一次就够了。
  • OTA 升级后属性不生效:先检查是不是 persist. 属性残留。用 getprop | grep persist 看看有没有旧值。
  • 多系统共存:每个系统用不同的 ro. 前缀,比如 ro.system1.xxxro.system2.xxx,避免冲突。

最后说一句:系统属性看似简单,但它是整个 Android 系统的“神经末梢”。改一个属性,可能影响十几个服务的行为。我见过最离谱的 bug,就是有人在 build.prop 里把 ro.build.version.sdk 从 33 改成了 34,结果所有判断 SDK 版本的逻辑全乱套了。

所以,改属性之前,先想清楚:这个属性是给谁读的?改了之后会不会有副作用?如果只是调试用,别往 build.prop 里写,用 setprop 临时改一下就够了。

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