Android 系统属性 prop 读写:自定义系统参数、固件配置参数持久保存
工控设备开机要记住上次配置——IP 地址、波特率、传感器校准值。靠 SharedPreferences 不够硬核,系统属性 prop 才是固件级的"刻入DNA"方案。
一、Android 系统属性机制简介
1.1 prop 是什么
Android 系统属性(System Property)是一套全局的键值对(key-value)存储机制,运行在 native 层,由属性服务(Property Service)统一管理。它有以下几个特点:
- 全局共享:所有进程都能读取,跨进程无需 IPC
- 内存存储:属性值存在共享内存中,读取速度极快
- 部分持久化:带
persist.前缀的属性会写入文件,重启不丢失 - 类型单一:只能存储字符串,数字也得存成字符串
简单理解:prop 就是一个系统级的 HashMap,所有 app 都能读,只有特定权限才能写。
1.2 persist 前缀的含义
属性名前缀决定了它的行为:
| 前缀 | 持久化 | 重启后 | 写入权限 |
|---|---|---|---|
persist. | ✅ 写入/data/property/persistent_properties | 保留 | system/root |
persist.sys. | ✅ 同上 | 保留 | system/root |
ro. | ❌ 只读属性 | 保留(编译时写入) | 不可修改 |
sys. | ❌ 仅内存 | 丢失 | system |
vendor. | ❌ 仅内存 | 丢失 | vendor |
debug. | ❌ 仅内存 | 丢失 | system |
ro.开头:read-only,编译时定死,运行时不可修改(如ro.product.model)persist.开头:可修改且持久化,重启后值依然存在- 其他前缀:可修改但不持久化,重启后恢复默认
工控固件配置必须用
persist.前缀,否则设备一断电配置就没了。
二、常用系统属性查看与修改
2.1 getprop 命令
通过 adb shell 查看系统属性:
# 查看所有属性getprop# 查看指定属性getprop ro.product.model getprop ro.build.version.release# 查看带前缀的所有属性getprop|greppersist.常用属性一览:
| 属性名 | 含义 |
|---|---|
ro.product.model | 设备型号 |
ro.build.version.release | Android 版本号 |
ro.build.version.sdk | SDK 版本号 |
ro.serialno | 设备序列号 |
ro.bootmode | 启动模式 |
persist.sys.locale | 系统语言 |
persist.sys.timezone | 时区 |
persist.sys.usb.config | USB 模式(adb/mtp) |
2.2 setprop 命令
修改属性值(需要 root 或系统权限):
# 设置属性setprop persist.sys.locale zh-CN# 设置自定义工控属性setprop persist.serial.baudrate115200setprop persist.serial.device /dev/ttyS1# 删除持久化属性(设为空字符串即可)setprop persist.myconfig""
setprop在普通 app 进程中执行不了,需要 adb root 或在系统源码中通过property_set()调用。
三、Android 源码中属性定义
3.1 build.prop 与 system.prop
Android 编译时,属性来源有多个文件:
| 文件路径 | 说明 |
|---|---|
build/tools/buildinfo.sh | 生成基础 build.prop |
device/<vendor>/<device>/system.prop | 设备厂商自定义属性 |
system/property_contexts | 属性 SELinux 上下文定义 |
default.prop/prop.default | 编译时注入的默认属性 |
最终编译输出到设备上的/system/build.prop、/vendor/build.prop、/odm/build.prop。
3.2 在源码中添加自定义属性
在设备目录下创建或编辑system.prop:
# 工控板串口配置 persist.serial.baudrate=9600 persist.serial.databits=8 persist.serial.parity=none persist.serial.stopbits=1 # 网关配置 persist.gateway.server_ip=192.168.1.100 persist.gateway.server_port=8080 persist.gateway.protocol=tcp # 传感器校准 persist.sensor.temp_offset=0.0 persist.sensor.humi_offset=0.03.3 property_service 的写入限制
不是什么属性都能随便写的。属性服务在property_service.cpp中定义了属性写入权限白名单:
// system/core/init/property_service.cppstaticconststruct{constchar*prefix;unsignedintuid;unsignedintgid;}property_perms[]={{"net.rmnet0.",AID_RADIO,0},{"net.gprs.",AID_RADIO,0},{"net.ppp.",AID_ROOT,0},{"persist.sys.",AID_SYSTEM,0},{"persist.service.",AID_SYSTEM,0},{"persist.",AID_SYSTEM,0},// persist.* 需要system权限{"sys.",AID_SYSTEM,0},// ...};要点:
persist.*属性写入需要system UID(即系统应用)- 普通第三方 app 无法直接
setprop - 需要将 app 签名为平台签名(
platform.pk8+platform.x509.pem)
四、APP 中读写系统属性:SystemProperties 反射调用
Android 隐藏了SystemProperties类(@hideAPI),普通 app 无法直接调用,但可以通过反射访问。
4.1 SystemProperties 反射封装
publicclassSystemPropUtils{privatestaticClass<?>sSystemPropertiesClass;privatestaticMethodsGetMethod;privatestaticMethodsSetMethod;privatestaticMethodsGetIntMethod;privatestaticMethodsGetBooleanMethod;static{try{sSystemPropertiesClass=Class.forName("android.os.SystemProperties");sGetMethod=sSystemPropertiesClass.getMethod("get",String.class);sSetMethod=sSystemPropertiesClass.getMethod("set",String.class,String.class);sGetIntMethod=sSystemPropertiesClass.getMethod("getInt",String.class,int.class);sGetBooleanMethod=sSystemPropertiesClass.getMethod("getBoolean",String.class,boolean.class);}catch(Exceptione){e.printStackTrace();}}/** * 读取字符串属性 */publicstaticStringget(Stringkey,Stringdef){try{return(String)sGetMethod.invoke(null,key);}catch(Exceptione){returndef;}}/** * 读取整型属性 */publicstaticintgetInt(Stringkey,intdef){try{return(int)sGetIntMethod.invoke(null,key,def);}catch(Exceptione){returndef;}}/** * 读取布尔属性 */publicstaticbooleangetBoolean(Stringkey,booleandef){try{return(boolean)sGetBooleanMethod.invoke(null,key,def);}catch(Exceptione){returndef;}}/** * 写入属性(需要system权限) */publicstaticvoidset(Stringkey,Stringvalue){try{sSetMethod.invoke(null,key,value);}catch(Exceptione){e.printStackTrace();}}}4.2 工控固件配置管理完整示例
publicclassFirmwareConfigManager{// 属性Key定义publicstaticfinalStringKEY_SERIAL_BAUDRATE="persist.serial.baudrate";publicstaticfinalStringKEY_SERIAL_DEVICE="persist.serial.device";publicstaticfinalStringKEY_GATEWAY_IP="persist.gateway.server_ip";publicstaticfinalStringKEY_GATEWAY_PORT="persist.gateway.server_port";publicstaticfinalStringKEY_GATEWAY_PROTOCOL="persist.gateway.protocol";publicstaticfinalStringKEY_SENSOR_TEMP_OFFSET="persist.sensor.temp_offset";publicstaticfinalStringKEY_DEVICE_ID="persist.device.id";// 默认值privatestaticfinalintDEFAULT_BAUDRATE=9600;privatestaticfinalStringDEFAULT_SERIAL_DEVICE="/dev/ttyS1";privatestaticfinalStringDEFAULT_GATEWAY_IP="192.168.1.100";privatestaticfinalintDEFAULT_GATEWAY_PORT=8080;privatestaticfinalStringDEFAULT_PROTOCOL="tcp";privatestaticvolatileFirmwareConfigManagersInstance;privateFirmwareConfigManager(){}publicstaticFirmwareConfigManagergetInstance(){if(sInstance==null){synchronized(FirmwareConfigManager.class){if(sInstance==null){sInstance=newFirmwareConfigManager();}}}returnsInstance;}// === 串口配置 ===publicintgetSerialBaudrate(){returnSystemPropUtils.getInt(KEY_SERIAL_BAUDRATE,DEFAULT_BAUDRATE);}publicvoidsetSerialBaudrate(intbaudrate){SystemPropUtils.set(KEY_SERIAL_BAUDRATE,String.valueOf(baudrate));}publicStringgetSerialDevice(){returnSystemPropUtils.get(KEY_SERIAL_DEVICE,DEFAULT_SERIAL_DEVICE);}publicvoidsetSerialDevice(Stringdevice){SystemPropUtils.set(KEY_SERIAL_DEVICE,device);}// === 网关配置 ===publicStringgetGatewayIp(){returnSystemPropUtils.get(KEY_GATEWAY_IP,DEFAULT_GATEWAY_IP);}publicvoidsetGatewayIp(Stringip){SystemPropUtils.set(KEY_GATEWAY_IP,ip);}publicintgetGatewayPort(){returnSystemPropUtils.getInt(KEY_GATEWAY_PORT,DEFAULT_GATEWAY_PORT);}publicvoidsetGatewayPort(intport){SystemPropUtils.set(KEY_GATEWAY_PORT,String.valueOf(port));}// === 传感器校准 ===publicfloatgetTempOffset(){Stringval=SystemPropUtils.get(KEY_SENSOR_TEMP_OFFSET,"0.0");returnFloat.parseFloat(val);}publicvoidsetTempOffset(floatoffset){SystemPropUtils.set(KEY_SENSOR_TEMP_OFFSET,String.valueOf(offset));}// === 设备ID ===publicStringgetDeviceId(){returnSystemPropUtils.get(KEY_DEVICE_ID,"UNKNOWN");}publicvoidsetDeviceId(Stringid){SystemPropUtils.set(KEY_DEVICE_ID,id);}/** * 打印当前所有配置(调试用) */publicvoidprintAllConfig(){Log.i("FirmwareConfig","=== 工控固件配置 ===");Log.i("FirmwareConfig","串口设备: "+getSerialDevice());Log.i("FirmwareConfig","波特率: "+getSerialBaudrate());Log.i("FirmwareConfig","网关IP: "+getGatewayIp());Log.i("FirmwareConfig","网关端口: "+getGatewayPort());Log.i("FirmwareConfig","通信协议: "+SystemPropUtils.get(KEY_GATEWAY_PROTOCOL,DEFAULT_PROTOCOL));Log.i("FirmwareConfig","温度偏移: "+getTempOffset());Log.i("FirmwareConfig","设备ID: "+getDeviceId());}}在 Activity 中使用:
publicclassConfigActivityextendsAppCompatActivity{privateFirmwareConfigManagermConfig;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_config);mConfig=FirmwareConfigManager.getInstance();mConfig.printAllConfig();// 读取配置填充UIetBaudrate.setText(String.valueOf(mConfig.getSerialBaudrate()));etGatewayIp.setText(mConfig.getGatewayIp());etGatewayPort.setText(String.valueOf(mConfig.getGatewayPort()));// 保存按钮btnSave.setOnClickListener(v->{mConfig.setSerialBaudrate(Integer.parseInt(etBaudrate.getText().toString()));mConfig.setGatewayIp(etGatewayIp.getText().toString());mConfig.setGatewayPort(Integer.parseInt(etGatewayPort.getText().toString()));Toast.makeText(this,"配置已保存(重启后生效)",Toast.LENGTH_SHORT).show();});}}五、工控固件配置持久化方案对比
| 方案 | 持久化 | 跨进程 | 权限要求 | 适用场景 |
|---|---|---|---|---|
| System Properties (persist.) | ✅ | ✅ 全局 | system 签名 | 固件级全局配置 |
| SharedPreferences | ✅ | ❌ 仅本app | 无 | app 内部配置 |
| 文件存储 (JSON/Properties) | ✅ | 需自行处理 | 文件权限 | 大数据量配置 |
| SQLite | ✅ | 需自行处理 | 无 | 结构化历史数据 |
| Settings.Global | ✅ | ✅ | WRITE_SETTINGS | 系统级简单配置 |
5.1 选择建议
- 全局固件配置(串口参数、网关地址)→ 用
persist.属性,native 层和 Java 层都能读 - App 内部偏好(界面主题、提醒开关)→ 用 SharedPreferences
- 大量历史数据(采集记录、报警日志)→ 用 SQLite 或文件
- 需要 native 层读取的参数→ 必须用 prop,SharedPreferences 在 native 层读不了
prop 的最大优势是跨语言——C/C++ 层用
property_get()读,Java 层用SystemProperties.get()读,Shell 用getprop读,三端共享同一份数据。这是其他方案做不到的。
六、常见工控 prop 属性汇总
| 属性名 | 类型 | 说明 | 默认值 |
|---|---|---|---|
persist.serial.baudrate | int | 串口波特率 | 9600 |
persist.serial.device | string | 串口设备节点 | /dev/ttyS1 |
persist.serial.parity | string | 校验方式 | none |
persist.gateway.server_ip | string | 网关服务器IP | 192.168.1.100 |
persist.gateway.server_port | int | 网关端口 | 8080 |
persist.gateway.protocol | string | 通信协议 | tcp |
persist.gateway.reconnect_interval | int | 重连间隔(ms) | 5000 |
persist.device.id | string | 设备唯一标识 | - |
persist.device.location | string | 设备安装位置 | - |
persist.sensor.temp_offset | float | 温度校准偏移 | 0.0 |
persist.sensor.humi_offset | float | 湿度校准偏移 | 0.0 |
persist.gpio.relay_default | int | 继电器默认状态 | 0 |
persist.net.eth_ip | string | 以太网静态IP | 192.168.1.88 |
persist.net.eth_gateway | string | 以太网网关 | 192.168.1.1 |
persist.net.eth_netmask | string | 以太网掩码 | 255.255.255.0 |
persist.watchdog.enable | boolean | 看门狗使能 | true |
persist.watchdog.timeout | int | 看门狗超时(s) | 30 |
6.1 native 层读写属性(C/C++)
#include<cutils/properties.h>// 读取属性charvalue[92]={0};intret=property_get("persist.serial.baudrate",value,"9600");// ret > 0 表示读取成功,value 为属性值// 写入属性(需要对应权限)property_set("persist.serial.baudrate","115200");在 native 服务进程中,直接调property_get/property_set即可读写同一份属性数据,与 Java 层完全同步。
6.2 通过 init.rc 设置默认值
在init.rc中可以在启动时设置属性默认值:
on boot# 设置工控默认配置setprop persist.serial.baudrate9600setprop persist.serial.device /dev/ttyS1 setprop persist.gateway.reconnect_interval5000setprop persist.watchdog.enable1注意:
setprop只在属性不存在时设置默认值才有意义。已存在的persist.属性重启后不会覆盖,会保留上次的值。如果需要强制重置,可以在 init.rc 中先setprop persist.xxx ""再设默认值——但这会清除用户配置,慎用。
系统属性是 Android 工控开发中最高效的全局配置方案。掌握 prop 的定义、读写、持久化机制,就能实现固件级参数管理:native 层和 Java 层共享配置,断电不丢失,跨进程零成本读取。这在定制固件、设备量产、远程运维等场景中是不可替代的能力。