8、系统属性(System Properties):属性设置与获取、属性文件管理、权限控制

系统属性,说白了就是Android系统里的全局键值对。每个属性都有一个名字和一个值,系统各个组件通过它来交换配置信息。我刚开始做嵌入式Android时,觉得这东西不就是个配置文件嘛,有啥好讲的?后来踩了几个坑才明白——系统属性虽然简单,但用不好真的会出大问题。

8.1 属性是什么?为什么需要它?

你想想看,一个Android设备上跑着那么多进程,它们之间怎么共享一些简单的配置信息?比如当前网络状态、系统语言、屏幕方向。用文件?太慢。用socket?太重。Android的做法就是搞一个全局的属性服务——property_service

属性以"名称=值"的形式存在,比如:

ro.build.version.sdk=34
persist.sys.timezone=Asia/Shanghai
sys.boot_completed=1

每个属性名用点号分隔成不同域段。前缀决定了属性的行为:

前缀 含义 特点
ro. 只读属性 设置后不可更改,重启后恢复
persist. 持久属性 重启后保留,写入/data/property
ctl. 控制属性 用于启动/停止系统服务
无前缀 普通属性 重启后丢失,运行时临时使用

核心要点:ro. 开头的属性一旦设置就不能改,直到下次重启。persist. 开头的属性会写入闪存,重启后还在。普通属性就是临时用用,重启就没了。

8.2 属性的设置与获取

在Android应用层,我们通过 SystemProperties 类来操作属性。不过要注意,这个类在SDK里是隐藏的,普通App用不了。只有系统应用或者有系统权限的应用才能调用。

8.2.1 Java层操作

// 获取属性
String value = SystemProperties.get("ro.build.version.sdk", "unknown");
int sdkVersion = SystemProperties.getInt("ro.build.version.sdk", 0);
boolean isEng = SystemProperties.getBoolean("ro.debuggable", false);

// 设置属性
SystemProperties.set("persist.sys.timezone", "Asia/Shanghai");
SystemProperties.set("ctl.start", "adbd");  // 启动adbd服务

我在项目中遇到过一个问题:用 SystemProperties.set() 设置属性后,另一个进程立刻去读,结果读不到新值。为什么?因为属性设置是异步的,设置后不会立即同步到所有进程。正确的做法是加一个短暂延时,或者用 SystemProperties.addChangeCallback() 监听变化。

8.2.2 Native层操作

C/C++层通过 property_getproperty_set 来操作:

#include <cutils/properties.h>

char value[PROP_VALUE_MAX];
property_get("ro.build.version.sdk", value, "unknown");

property_set("persist.sys.timezone", "Asia/Shanghai");

我的习惯:在Native代码里操作属性时,一定要检查返回值。property_get返回的是实际读取到的字符串长度,如果返回0,说明属性不存在或者值为空。别直接拿空字符串去用,容易出bug。

8.2.3 命令行操作

调试时最常用的就是 getpropsetprop

# 查看所有属性
adb shell getprop

# 查看单个属性
adb shell getprop ro.build.version.sdk

# 设置属性
adb shell setprop persist.sys.timezone "Asia/Shanghai"

嗯,这里要注意:setprop 只能设置普通属性和persist属性,不能设置ro.开头的只读属性。我曾经试过在adb里直接改ro.serialno,结果报错——权限不够。

8.3 属性文件管理

系统属性不是凭空产生的,它们来自多个属性文件。系统启动时,init进程会按顺序加载这些文件:

  1. /default.prop — 编译时生成的默认属性
  2. /system/build.prop — 系统镜像的构建属性
  3. /vendor/build.prop — 厂商定制的属性
  4. /odm/build.prop — ODM层属性
  5. /data/property/* — 用户空间的持久属性

加载顺序决定了优先级——后加载的会覆盖先加载的。所以 /data/property/ 里的属性优先级最高,因为它最后加载。

我曾经踩过的坑:有一次我在 /system/build.prop 里加了一个自定义属性,编译刷机后死活读不到。查了半天才发现,/vendor/build.prop 里也有同名属性,把我的覆盖了。记住:vendor层的属性会覆盖system层,别搞混了。

属性文件是纯文本格式,每行一个属性:

ro.product.model=MyDevice
ro.product.manufacturer=MyCompany
persist.sys.timezone=Asia/Shanghai
# 这是注释行,会被忽略

修改属性文件后,需要重启设备才能生效。不过对于persist属性,你可以在运行时用 setprop 设置,它会自动写入 /data/property/ 目录。

8.4 权限控制

系统属性不是谁都能随便改的。Android有一套严格的权限控制机制,说白了就是——谁有资格设置哪个属性。

8.4.1 SELinux策略

每个属性都有一个SELinux上下文。只有拥有对应权限的进程才能设置该属性。比如:

# 在 .te 文件中定义
set_prop(radio, radio_prop)
set_prop(system_app, system_prop)
set_prop(init, ctl_prop)

这意味着radio进程只能设置radio相关的属性,system_app只能设置系统属性,init进程才能控制服务启停(ctl.前缀)。

8.4.2 属性白名单

除了SELinux,Android还有一个属性白名单机制。在 /system/etc/property_contexts 文件中定义了每个属性的访问权限:

ro.               u:object_r:system_prop:s0
persist.          u:object_r:system_prop:s0
ctl.              u:object_r:ctl_prop:s0
persist.radio.    u:object_r:radio_prop:s0
persist.bluetooth. u:object_r:bluetooth_prop:s0

你看,persist.radio. 开头的属性只有radio用户才能设置。普通App就算有系统权限,也改不了这些属性。

避坑指南:我曾经在开发一个系统应用时,想通过设置 persist.radio.network.mode 来切换网络模式。代码写好了,跑起来也没报错,但属性就是没变。后来发现——我的应用没有radio权限。记住:不报错不代表成功了,一定要用getprop确认属性是否真的被修改。

8.4.3 应用层权限

普通App想读取系统属性?可以,但有限制。从Android 10开始,非系统应用只能读取部分属性,比如 ro.build.version.sdkro.serialno 等。想读取更多属性,需要在manifest里声明权限:

<uses-permission android:name="android.permission.READ_PROPERTIES" />

但这个权限是系统级别的,普通App申请不到。说白了,系统属性就是给系统组件用的,普通应用别想碰。

8.5 实战:自定义系统属性

在实际的嵌入式项目中,我们经常需要添加自定义属性。比如设备序列号、硬件版本号、校准参数等。我的做法是这样的:

  1. /vendor/build.prop 里添加只读属性,比如 ro.product.hw.version=1.2
  2. 在init.rc脚本里用 setprop 设置运行时属性
  3. 对于需要持久保存的参数,用 persist. 前缀

举个例子,假设我们要保存设备的校准参数:

// 校准完成后写入
SystemProperties.set("persist.vendor.calib.status", "done");
SystemProperties.set("persist.vendor.calib.offset_x", "12.5");
SystemProperties.set("persist.vendor.calib.offset_y", "-3.2");

// 下次启动时读取
String calibStatus = SystemProperties.get("persist.vendor.calib.status", "none");
if ("done".equals(calibStatus)) {
    float offsetX = Float.parseFloat(
        SystemProperties.get("persist.vendor.calib.offset_x", "0"));
    // 应用校准参数
}

我的建议:自定义属性最好加上厂商前缀,比如 persist.vendor.ro.vendor.。这样既避免和系统属性冲突,也方便后续维护。别图省事直接用 persist.myapp.,万一和其他应用重名了,调试起来很痛苦。

8.6 知识体系总览

下面这张图总结了系统属性的核心逻辑:

系统属性知识体系 System Properties 属性操作 Java: SystemProperties.get/set Native: property_get/set Shell: getprop / setprop 属性文件管理 /default.prop (默认) /system/build.prop (系统) /vendor/build.prop (厂商) /data/property/ (持久) 权限控制 SELinux上下文 属性白名单 应用层权限 (READ_PROPERTIES) 核心原则:只读属性不可改,持久属性写闪存,普通属性临时用

系统属性看似简单,但它是Android系统运行的基础设施之一。从属性文件的加载顺序,到SELinux的权限控制,再到不同前缀的行为差异——每一个细节都可能成为你调试时的拦路虎。记住:操作属性时,先确认权限,再确认前缀,最后确认值是否真的写进去了。这三步走下来,基本不会出大问题。

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