第25章 系统属性(System Properties)

系统属性,说白了就是Android系统里的全局键值对。每个属性都是一个字符串键和一个字符串值,系统各个组件通过它来交换配置信息。我刚开始接触Android Framework时,觉得这东西不就是个配置文件嘛,后来才发现——嗯,它远比我想象的要复杂。

属性系统原理

Android的属性系统,核心是一个共享内存区域。所有进程都能读,但只有少数进程能写。这个设计很有意思——你想想看,如果每个进程都随意改属性,系统早就乱套了。

具体来说,属性系统分三层:

  • 属性存储层:一块mmap出来的共享内存,所有进程都能映射到自己的地址空间
  • 属性服务层:init进程启动时开启的property_service,监听/dev/socket/property_service这个socket
  • 客户端访问层:通过libcutils提供的API来读写属性

我曾在一次性能优化项目中,发现系统启动时大量进程同时读属性,导致init进程的socket队列爆满。后来怎么解决的?把频繁读取的属性缓存到进程本地,减少对属性服务的调用。

核心流程:设置属性时,客户端通过socket发送请求给init进程,init进程检查权限后更新共享内存,然后通过socket通知所有监听该属性的进程。读取属性时,客户端直接读共享内存,不需要经过init进程。

Android属性系统架构图 属性存储层(共享内存) 属性服务层(init进程) 客户端访问层(libcutils) 写属性:socket请求 → 读属性:直接读共享内存 → 权限控制(selinux + uid/gid) 属性监听(socket通知) 持久化(persist属性) 属性系统是Android配置管理的基石,理解它才能用好它

属性读写API

在Android开发中,我们主要通过JNI调用C层的API来读写属性。Java层封装在SystemProperties这个隐藏类里。

读取属性

// C层API
#include <cutils/properties.h>

char value[PROP_VALUE_MAX];
property_get("ro.build.version.sdk", value, "unknown");
// value就是获取到的属性值,如果不存在则返回默认值"unknown"

// Java层(隐藏API)
import android.os.SystemProperties;

String sdkVersion = SystemProperties.get("ro.build.version.sdk", "unknown");
int sdkInt = SystemProperties.getInt("ro.build.version.sdk", 0);
boolean isEng = SystemProperties.getBoolean("ro.debuggable", false);

个人经验:我习惯在读取属性时总是提供默认值。为什么?因为属性可能不存在,或者进程没有权限读取。不给默认值,代码就可能抛出异常。曾经有个同事没给默认值,结果在某个定制ROM上直接崩溃了。

设置属性

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

// Java层
SystemProperties.set("persist.sys.timezone", "Asia/Shanghai");

设置属性时要注意,只有系统进程或有root权限的进程才能设置属性。普通应用调用set方法会直接失败。

避坑指南:我曾经在系统服务中频繁调用property_set来更新状态,结果发现性能很差。后来定位到原因——每次set都会触发socket通信和权限检查,高频调用会导致init进程CPU飙升。解决方案是批量更新,或者改用其他通信方式。

属性权限控制

属性权限控制,说白了就是「谁能读、谁能写」。Android通过两种机制来管控:

机制 说明 我的看法
SELinux策略 定义哪些domain可以访问哪些属性 这是最底层的防线,绕不过去
UID/GID检查 只有system、root等特定UID才能写属性 简单粗暴,但有效
属性前缀控制 不同前缀的属性有不同的写权限 比如ro.开头的只读,persist.开头的可持久化

具体来说,属性前缀决定了它的行为:

  • ro.:只读属性,一旦设置就不能再修改。我见过有人试图在运行时改ro.build.fingerprint,结果当然是失败
  • persist.:持久化属性,会写入/data/property目录,重启后保留
  • ctl.:控制属性,用于控制init服务的启动和停止
  • 其他:普通属性,重启后丢失

权限检查流程:当进程调用property_set时,init进程会先检查SELinux策略,确认该进程的domain是否有权限写这个属性。然后检查UID,只有system、root或特定服务才能写。最后检查属性前缀,比如普通进程不能写ro.开头的属性。

常用系统属性分析

Android系统里有上千个属性,但常用的也就那么几十个。我挑几个重点说说:

构建相关属性

ro.build.version.sdk = 33        // Android API Level
ro.build.version.release = 13    // Android版本号
ro.build.fingerprint = ...       // 构建指纹,用于OTA升级匹配
ro.product.model = Pixel 7       // 设备型号
ro.product.manufacturer = Google // 制造商

这些属性在系统启动时由init进程从build.prop文件中加载。我曾在做设备适配时,发现某个App通过读取ro.product.model来判断设备类型,结果因为定制ROM改了这个值导致App崩溃。嗯,这就是为什么Google建议用API Level而不是设备型号来做判断。

调试相关属性

ro.debuggable = 1       // 1表示可调试,user版本通常为0
ro.secure = 0           // 0表示非安全模式,允许adb root
persist.sys.usb.config = mtp,adb  // USB连接模式

这些属性在开发调试时非常有用。我记得有一次,客户反馈说他们的设备adb连不上,我一看——ro.debuggable被设成了0,而且persist.sys.usb.config里没有adb。这就是典型的工程配置问题。

性能相关属性

dalvik.vm.heapsize = 256m        // Dalvik/ART堆大小
dalvik.vm.heapgrowthlimit = 128m // 应用堆增长限制
ro.sf.lcd_density = 480          // 屏幕密度
debug.sf.hw = 1                  // 是否启用硬件加速

这些属性直接影响App的运行表现。我曾经优化过一个内存泄漏的应用,发现它频繁触发GC,后来检查发现dalvik.vm.heapsize被设得太小。调整后,应用流畅度明显提升。

网络相关属性

net.dns1 = 8.8.8.8        // DNS服务器
net.dns2 = 8.8.4.4
net.hostname = android-xxx // 主机名
dhcp.eth0.dns1 = ...       // DHCP获取的DNS

网络属性是动态变化的,由netd守护进程管理。我遇到过一个问题:设备切换WiFi后,DNS没有及时更新,导致部分域名解析失败。后来发现是net.dns1属性没有正确刷新,重启网络服务后才解决。

实用技巧:如果你想查看当前系统所有属性,可以用adb shell getprop命令。输出结果会列出所有属性和对应的值。我个人习惯把这个输出保存下来,方便对比不同版本之间的差异。

好了,关于系统属性,核心内容就这些。理解属性系统的原理,掌握读写API,注意权限控制,熟悉常用属性——这四步走完,你就能在Android Framework开发中游刃有余地使用系统属性了。


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