引言
在之前的文章中,我们见证了init进程如何"开天辟地",AMS如何管理应用生命周期。但是,Android系统如何存储和管理全局配置?如何让不同进程共享配置信息?如何控制配置的访问权限?
答案就是属性系统(Property System)------Android的"全局配置中心"。
想象这样的场景:
- 你修改屏幕亮度,系统立即生效
- 你开启飞行模式,所有网络服务自动关闭
- 应用查询系统版本号,瞬间获得结果
- init进程根据属性值决定是否启动某个服务
这些看似简单的操作,背后都依赖属性系统。
ini
属性系统的核心作用:
┌───────────────────────────────────────┐
│ 全局配置存储 │
│ - ro.build.version.sdk = 35 │ (只读,系统版本)
│ - persist.sys.timezone = Asia/... │ (持久化,时区)
│ - sys.boot_completed = 1 │ (临时,启动状态)
└────────────┬──────────────────────────┘
│
共享给所有进程访问
│
┌─────────┼──────────┐
│ │ │
init 应用进程 系统服务
│ │ │
控制服务 查询配置 监听变更
📖 系列前置阅读:建议先阅读第12篇(init进程与属性系统基础),理解init在属性系统中的核心角色。
属性系统是什么?
属性系统的定义
属性系统(Property System) 是Android提供的一个全局键值对存储机制,用于:
- 存储系统配置信息(版本号、设备信息、用户设置等)
- 进程间共享配置数据(通过共享内存实现)
- 触发系统事件(属性变更 → 触发器 → 执行动作)
- 控制服务行为(启动/停止服务、调整参数等)
属性的分类
根据属性名前缀,属性分为不同类型:
| 前缀 | 类型 | 说明 | 示例 |
|---|---|---|---|
ro. |
只读属性 | 系统启动后不可修改 | ro.build.version.sdk=35 |
persist. |
持久化属性 | 重启后保留 | persist.sys.timezone=Asia/Shanghai |
sys. |
系统属性 | 临时属性,重启后丢失 | sys.boot_completed=1 |
ctl. |
控制属性 | 用于控制服务启停 | ctl.start=zygote |
debug. |
调试属性 | 用于调试开关 | debug.atrace.tags.enableflags=0 |
| 无前缀 | 普通属性 | 各模块自定义 | vendor.camera.aux.packagelist=... |
属性值的限制
cpp
// system/core/property_service/libpropertyinfoparser/property_info_parser.cpp
#define PROP_NAME_MAX 92 // 属性名最大长度
#define PROP_VALUE_MAX 92 // 属性值最大长度
重要限制:
- 属性名长度: 最多92个字符
- 属性值长度: 最多92个字符
- 属性数量: 系统限制(Android 15约为~2000个)
属性系统架构
整体架构图
为了帮助理解属性系统的完整架构,下图展示了从共享内存到应用访问的全链路:

图:属性系统架构,init进程管理共享内存,SELinux控制访问权限,应用通过Binder或直接内存访问读取属性
这张图涵盖了:
- 共享内存层: /dev/__properties__内存映射
- init进程: 属性服务管理者,处理写请求
- SELinux层: 属性权限控制(property_contexts)
- 访问接口: Java/Native/Shell三层API
- 触发器机制: 属性变更触发init.rc中的Action
接下来,我们将逐一深入这些核心机制的源码实现。
共享内存实现原理
为什么使用共享内存?
传统的进程间通信(Binder、Socket)每次读取都需要跨进程调用,开销较大。属性系统采用共享内存设计:
优势:
- ✅ 读取速度快(直接内存访问,无需Binder调用)
- ✅ 支持高频读取(系统属性被频繁查询)
- ✅ 内存占用小(所有进程共享同一块内存)
劣势:
- ❌ 写入需要保护(通过init进程的Socket接口控制)
- ❌ 属性数量有限(共享内存大小固定)
共享内存布局
ini
/dev/__properties__ (共享内存文件,128KB)
┌─────────────────────────────────────────────────┐
│ Header (头部,记录属性区域信息) │
│ - magic: 0x00504F4D (识别标记) │
│ - version: 2 (格式版本) │
│ - num_prop_areas: 1 (属性区域数量) │
├─────────────────────────────────────────────────┤
│ Property Area 1 (属性区域,存储键值对) │
│ ┌───────────────────────────────────────────┐ │
│ │ ro.build.version.sdk = 35 │ │
│ │ (name: 28字节, value: 92字节) │ │
│ ├───────────────────────────────────────────┤ │
│ │ persist.sys.timezone = Asia/Shanghai │ │
│ ├───────────────────────────────────────────┤ │
│ │ sys.boot_completed = 1 │ │
│ ├───────────────────────────────────────────┤ │
│ │ ... (更多属性) │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
共享内存初始化源码
cpp
// system/core/init/property_service.cpp (Android 15)
void PropertyInit() {
// 1. 创建共享内存文件 /dev/__properties__
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
// 2. 创建属性区域(Property Area)
// 每个区域128KB,最多支持~2000个属性
CreateSerializedPropertyInfo();
// 3. 初始化属性服务Socket
// 用于接收其他进程的写请求
if (!InitPropertySet("u:object_r:properties_serial:s0")) {
LOG(FATAL) << "Failed to initialize property set";
}
// 4. 加载默认属性
// 从 /system/etc/prop.default 加载
LoadPropertiesFromFile("/system/etc/prop.default");
// 5. 加载持久化属性
// 从 /data/property 加载 persist.* 属性
LoadPersistentProperties();
// 6. 启动属性服务Socket监听
StartPropertyService(&property_set_fd);
}
属性读取机制
Java层读取
java
// frameworks/base/core/java/android/os/SystemProperties.java
public class SystemProperties {
/**
* 读取字符串属性
* @param key 属性名
* @param def 默认值
* @return 属性值
*/
public static String get(String key, String def) {
// 调用Native方法
String ret = native_get(key, def);
return ret;
}
/**
* 读取整数属性
*/
public static int getInt(String key, int def) {
String value = get(key);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return def;
}
}
/**
* 读取布尔属性
*/
public static boolean getBoolean(String key, boolean def) {
String value = get(key);
if (value == null || value.length() == 0) {
return def;
}
return "1".equals(value) || "true".equalsIgnoreCase(value)
|| "y".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value)
|| "on".equalsIgnoreCase(value);
}
// Native方法声明
private static native String native_get(String key, String def);
}
Native层读取
cpp
// system/core/libcutils/properties.cpp
int property_get(const char *key, char *value, const char *default_value) {
// 1. 从共享内存中查找属性
const prop_info *pi = __system_property_find(key);
if (pi == nullptr) {
// 属性不存在,返回默认值
if (default_value) {
strlcpy(value, default_value, PROP_VALUE_MAX);
return strlen(value);
}
return 0;
}
// 2. 读取属性值(直接从共享内存读取,速度极快)
return __system_property_read(pi, nullptr, value);
}
// 从共享内存查找属性
const prop_info* __system_property_find(const char *name) {
// 使用Trie树(前缀树)快速查找
// 共享内存中的属性以Trie树组织,查找时间复杂度O(m),m为属性名长度
return prop_area->find(name);
}
性能优化:
- 共享内存直接读取,无Binder开销
- Trie树索引,查找速度O(m)
- 属性值缓存,避免重复解析
Shell层读取
bash
# getprop命令读取属性
getprop ro.build.version.sdk
# 输出: 35
# 读取所有属性
getprop | grep "ro.build"
# 输出示例:
# [ro.build.date]: [Thu Jan 10 12:00:00 UTC 2026]
# [ro.build.id]: [AP3A.241105.008]
# [ro.build.version.sdk]: [35]
# [ro.build.version.release]: [15]
属性写入机制
写入流程
属性写入必须通过init进程的Socket接口,以保证权限控制和触发器执行:
scss
应用进程
↓ (调用SystemProperties.set())
通过Socket连接到init进程
↓ (/dev/socket/property_service)
init进程接收请求
↓
1. 权限检查(SELinux)
↓
2. 写入共享内存
↓
3. 通知其他进程(属性变更)
↓
4. 执行触发器(如果有)
Java层写入
java
// frameworks/base/core/java/android/os/SystemProperties.java
public class SystemProperties {
/**
* 写入属性
* 需要权限: android.permission.WRITE_SETTINGS 或 root权限
*/
public static void set(String key, String val) {
// 权限检查
if (!checkPermission()) {
throw new SecurityException("Requires WRITE_SETTINGS permission");
}
// 调用Native方法,通过Socket发送到init进程
native_set(key, val);
// 等待属性写入完成
PropertyInvalidatedCache.invalidateCache(key);
}
private static native void native_set(String key, String val);
}
Native层写入
cpp
// system/core/init/property_service.cpp
void HandlePropertySet(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr,
SocketConnection* socket, std::string* error) {
// 1. 权限检查(SELinux)
if (!CheckPermissions(name, value, source_context, cr, error)) {
return; // 权限不足,拒绝写入
}
// 2. 特殊属性处理:ctl.* 用于控制服务
if (StartsWith(name, "ctl.")) {
HandleControlProperty(name, value, error);
return;
}
// 3. 持久化属性:persist.* 需要写入文件
if (StartsWith(name, "persist.")) {
WritePersistentProperty(name, value);
}
// 4. 只读属性:ro.* 只能在启动阶段设置,之后禁止修改
if (StartsWith(name, "ro.") && IsBootCompleted()) {
*error = "Read-only property was already set";
return;
}
// 5. 写入共享内存
uint32_t* serial = __system_property_serial(prop_info);
*serial = *serial + 1; // 序列号+1,通知监听者
__system_property_update(prop_info, value.c_str(), value.size());
*serial = *serial + 1; // 再次+1,完成写入
// 6. 执行触发器(如果init.rc中定义了on property:xxx=yyy)
ActionManager::GetInstance().QueuePropertyChange(name, value);
}
权限检查机制
属性写入受到SELinux 严格控制,权限定义在property_contexts:
bash
# /system/etc/selinux/plat_property_contexts
# 格式: 属性名前缀 SELinux类型
# 只有init进程可以设置ro.*属性
ro. u:object_r:default_prop:s0
# persist.*属性可以被system_server设置
persist.sys. u:object_r:system_prop:s0
# debug.*属性可以被shell和adb设置
debug. u:object_r:debug_prop:s0
# 自定义供应商属性
vendor.camera. u:object_r:vendor_camera_prop:s0
属性触发器机制
触发器的作用
init.rc中可以定义属性触发器,当属性值变化时自动执行命令:
bash
# /system/etc/init/init.rc
# 当sys.boot_completed=1时,执行以下命令
on property:sys.boot_completed=1
# 启动bootanim服务(开机动画)
start bootanim
# 清理临时文件
exec - system system -- /system/bin/rm -rf /data/local/tmp/*
# 当persist.sys.timezone变化时,更新时区
on property:persist.sys.timezone=*
# 重启时区守护进程
restart timezone-updater
# 当ro.debuggable=1时(调试模式),启用额外日志
on property:ro.debuggable=1
# 设置日志级别
setprop persist.log.tag V
# 启动调试服务
start adbd
触发器执行流程
cpp
// system/core/init/action_manager.cpp
void ActionManager::QueuePropertyChange(const std::string& name,
const std::string& value) {
// 1. 遍历所有注册的Action
for (const auto& action : actions_) {
// 2. 检查Action的触发条件
if (action->CheckPropertyTrigger(name, value)) {
// 3. 将Action加入执行队列
event_queue_.emplace(action);
}
}
// 4. 唤醒init主循环,执行队列中的Action
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, property_fd, &ev);
}
实战应用场景
场景1: 查询系统信息
java
// 获取系统版本号
int sdkVersion = SystemProperties.getInt("ro.build.version.sdk", 0);
// 输出: 35 (Android 15)
// 获取设备型号
String model = SystemProperties.get("ro.product.model", "Unknown");
// 输出: Pixel 8 Pro
// 获取系统启动状态
boolean bootCompleted = SystemProperties.getBoolean("sys.boot_completed", false);
// true表示启动完成
// 获取时区
String timezone = SystemProperties.get("persist.sys.timezone", "UTC");
// 输出: Asia/Shanghai
场景2: 控制服务启停
bash
# 启动zygote服务
setprop ctl.start zygote
# 停止surfaceflinger服务
setprop ctl.stop surfaceflinger
# 重启audioserver服务
setprop ctl.restart audioserver
原理:
- 设置
ctl.start/stop/restart属性时,init进程会执行相应的服务控制命令 - 这是Android推荐的服务控制方式(替代直接kill进程)
场景3: 调试开关
bash
# 启用systrace追踪
setprop debug.atrace.tags.enableflags 0x1000
# 启用SurfaceFlinger调试日志
setprop debug.sf.showupdates 1
# 启用输入事件日志
setprop debug.input.trace 1
# 关闭硬件加速(用于调试渲染问题)
setprop debug.hwui.disable_vsync true
场景4: 监听属性变更
cpp
// Native代码监听属性变更
#include <sys/system_properties.h>
void OnPropertyChange(const char *name, const char *value, void *cookie) {
printf("Property %s changed to %s\n", name, value);
// 执行相应操作
if (strcmp(name, "persist.sys.locale") == 0) {
// 重新加载语言配置
ReloadLocale(value);
}
}
int main() {
// 注册属性变更监听器
__system_property_set_callback(OnPropertyChange, nullptr);
// 主循环
while (true) {
sleep(1);
}
}
持久化属性
persist.*属性的存储
persist.*前缀的属性会持久化到磁盘,重启后保留:
bash
# 持久化属性存储位置
/data/property/
├── persist.sys.timezone # 单个文件存储单个属性
├── persist.sys.locale
├── persist.sys.usb.config
└── ...
# 查看持久化属性
ls -l /data/property/
# -rw------- 1 system system 15 Jan 10 12:00 persist.sys.timezone
# 读取持久化属性内容
cat /data/property/persist.sys.timezone
# Asia/Shanghai
持久化源码实现
cpp
// system/core/init/persistent_properties.cpp
void WritePersistentProperty(const std::string& name, const std::string& value) {
// 1. 检查是否为persist.*属性
if (!StartsWith(name, "persist.")) {
return;
}
// 2. 构建文件路径: /data/property/persist.*
std::string path = "/data/property/" + name;
// 3. 写入文件(权限: 0600, owner: system)
WriteStringToFile(value, path, 0600, AID_SYSTEM, AID_SYSTEM);
// 4. 同步到磁盘(确保数据落盘)
sync();
}
void LoadPersistentProperties() {
// 1. 读取 /data/property/ 目录
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir("/data/property"), closedir);
// 2. 遍历所有文件
struct dirent* entry;
while ((entry = readdir(dir.get())) != nullptr) {
std::string name = entry->d_name;
// 3. 只加载persist.*文件
if (!StartsWith(name, "persist.")) {
continue;
}
// 4. 读取文件内容
std::string value;
ReadFileToString("/data/property/" + name, &value);
// 5. 设置属性(加载到共享内存)
__system_property_set(name.c_str(), value.c_str());
}
}
Android 15的属性系统增强
1. 属性安全增强
Android 15加强了属性访问控制:
cpp
// system/core/init/property_service.cpp (Android 15新增)
bool CheckPropertyPermissions(const std::string& name,
const std::string& value,
const std::string& source_context,
const ucred& cr) {
// 1. 检查SELinux权限
if (!CheckMacPerms(name, source_context)) {
return false;
}
// 2. 新增:检查属性名合法性(防止路径遍历攻击)
if (name.find("..") != std::string::npos ||
name.find("/") != std::string::npos) {
return false; // 拒绝包含..或/的属性名
}
// 3. 新增:检查属性值长度限制
if (value.size() > PROP_VALUE_MAX) {
return false;
}
// 4. 新增:限制高频写入(防止DoS攻击)
if (!ThrottlePropertyWrites(name, cr.uid)) {
return false;
}
return true;
}
2. 属性变更通知优化
Android 15优化了属性变更通知机制,减少CPU唤醒:
cpp
// 批量通知,减少唤醒次数
void NotifyPropertyChangeBatch(const std::vector<std::string>& names) {
// 合并多个属性变更为一次通知
for (const auto& name : names) {
property_change_queue_.push(name);
}
// 延迟100ms后统一通知,避免频繁唤醒
if (!notification_pending_) {
notification_pending_ = true;
timer.schedule([]() {
FlushPropertyChangeQueue();
notification_pending_ = false;
}, 100ms);
}
}
3. 属性缓存优化
Android 15引入了属性缓存,进一步提升读取性能:
java
// frameworks/base/core/java/android/os/SystemProperties.java (Android 15)
public class SystemProperties {
// 属性缓存(LRU Cache,最多缓存100个高频属性)
private static final PropertyCache sCache = new PropertyCache(100);
public static String get(String key, String def) {
// 1. 先查缓存
String cached = sCache.get(key);
if (cached != null) {
return cached;
}
// 2. 缓存未命中,从共享内存读取
String value = native_get(key, def);
// 3. 存入缓存
sCache.put(key, value);
return value;
}
}
常见问题(FAQ)
Q1: 为什么属性值长度限制为92字节?
A:
- 历史原因:早期Android设计时,为了节省共享内存空间
- 兼容性:修改限制会破坏现有应用
- 解决方案:如果需要存储长内容,可以:
- 使用多个属性拆分存储
- 存储到文件,属性只记录文件路径
- 使用SharedPreferences或数据库
Q2: 如何在应用中监听属性变更?
A: 应用无法直接监听(需要Native代码),推荐方案:
- 使用广播:系统属性变更时发送广播
- 使用ContentObserver:监听Settings数据库
- 定时轮询:不推荐,耗电
Q3: persist.*属性何时生效?
A:
- 写入后立即生效(写入共享内存)
- 同时写入磁盘(/data/property/),重启后自动加载
- 如果/data分区未挂载(如Recovery模式),persist.*属性不会加载
Q4: 为什么我的应用无法修改属性?
A: 可能的原因:
- 权限不足 :需要
android.permission.WRITE_SETTINGS权限或root权限 - SELinux拒绝 :检查
property_contexts是否允许修改该属性 - 只读属性:ro.*属性在启动后无法修改
- 应用沙箱:部分属性只能由系统进程修改
Q5: 如何调试属性相关问题?
bash
# 1. 查看所有属性
adb shell getprop
# 2. 查看属性变更日志
adb logcat -b main -s PropertyService:V
# 3. 查看SELinux拒绝日志
adb logcat -b main | grep "avc: denied"
# 4. 测试属性读写
adb shell
setprop debug.test.prop "test_value"
getprop debug.test.prop
实战:自定义属性应用
场景:智能座舱模式切换
假设你在开发智能座舱系统,需要根据驾驶模式切换不同的配置。
1. 定义自定义属性
bash
# 在 /vendor/etc/selinux/vendor_property_contexts 定义权限
vendor.ivi.mode u:object_r:vendor_ivi_prop:s0
2. 在init.rc中定义触发器
bash
# /vendor/etc/init/ivi.rc
# 驾驶模式:关闭娱乐功能,启用导航
on property:vendor.ivi.mode=driving
stop media_server
stop game_service
start navigation_service
setprop vendor.audio.mode driving
# 停车模式:启用全部娱乐功能
on property:vendor.ivi.mode=parking
start media_server
start game_service
stop navigation_service
setprop vendor.audio.mode parking
# 乘客模式:启用后排娱乐
on property:vendor.ivi.mode=passenger
start rear_entertainment
setprop vendor.display.mode dual
3. 应用层控制
java
// 智能座舱控制器
public class IVIModeController {
public static void setDrivingMode() {
SystemProperties.set("vendor.ivi.mode", "driving");
// init进程会自动执行on property触发器
}
public static void setParkingMode() {
SystemProperties.set("vendor.ivi.mode", "parking");
}
public static void setPassengerMode() {
SystemProperties.set("vendor.ivi.mode", "passenger");
}
public static String getCurrentMode() {
return SystemProperties.get("vendor.ivi.mode", "driving");
}
}
4. 监听模式变更
java
// 服务监听模式变更
public class MediaService extends Service {
@Override
public void onCreate() {
// 注册属性变更广播
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.PROPERTY_CHANGED");
registerReceiver(mModeChangeReceiver, filter);
}
private BroadcastReceiver mModeChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String mode = SystemProperties.get("vendor.ivi.mode", "");
if ("parking".equals(mode)) {
// 进入停车模式,启用全部功能
enableAllFeatures();
} else if ("driving".equals(mode)) {
// 进入驾驶模式,禁用娱乐功能
disableEntertainment();
}
}
};
}
总结
本文深度剖析了Android属性系统的核心机制:
核心要点回顾
-
属性系统定义:
- 全局键值对存储,通过共享内存实现高效访问
- 分类:ro(只读)、persist(持久化)、sys(临时)、ctl(控制)等
-
共享内存实现:
- /dev/__properties__共享内存文件(128KB)
- Trie树索引,查找时间O(m)
- 读取速度快,无Binder开销
-
属性读写机制:
- 读取:直接从共享内存读取,速度极快
- 写入:通过init进程的Socket接口,受SELinux控制
-
权限控制:
- SELinux策略文件(property_contexts)定义访问权限
- 不同前缀的属性有不同的访问控制
-
触发器机制:
- init.rc中定义on property:xxx=yyy触发器
- 属性变更时自动执行命令
-
持久化机制:
- persist.*属性存储在/data/property/
- 重启后自动加载
-
Android 15增强:
- 属性安全增强(防路径遍历、限流)
- 变更通知优化(批量通知,减少唤醒)
- 属性缓存(LRU Cache,提升性能)
参考资料
系列文章
本文基于Android 15 (API Level 35)源码分析,不同厂商的定制ROM可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品