属性系统与系统配置管理:Android的全局配置中心

引言

在之前的文章中,我们见证了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: 可能的原因:

  1. 权限不足 :需要android.permission.WRITE_SETTINGS权限或root权限
  2. SELinux拒绝 :检查property_contexts是否允许修改该属性
  3. 只读属性:ro.*属性在启动后无法修改
  4. 应用沙箱:部分属性只能由系统进程修改

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属性系统的核心机制:

核心要点回顾

  1. 属性系统定义:

    • 全局键值对存储,通过共享内存实现高效访问
    • 分类:ro(只读)、persist(持久化)、sys(临时)、ctl(控制)等
  2. 共享内存实现:

    • /dev/__properties__共享内存文件(128KB)
    • Trie树索引,查找时间O(m)
    • 读取速度快,无Binder开销
  3. 属性读写机制:

    • 读取:直接从共享内存读取,速度极快
    • 写入:通过init进程的Socket接口,受SELinux控制
  4. 权限控制:

    • SELinux策略文件(property_contexts)定义访问权限
    • 不同前缀的属性有不同的访问控制
  5. 触发器机制:

    • init.rc中定义on property:xxx=yyy触发器
    • 属性变更时自动执行命令
  6. 持久化机制:

    • persist.*属性存储在/data/property/
    • 重启后自动加载
  7. Android 15增强:

    • 属性安全增强(防路径遍历、限流)
    • 变更通知优化(批量通知,减少唤醒)
    • 属性缓存(LRU Cache,提升性能)

参考资料

  1. AOSP 15.0源码 - property_service.cpp
  2. AOSP 15.0源码 - SystemProperties.java
  3. SELinux Property Contexts

系列文章


本文基于Android 15 (API Level 35)源码分析,不同厂商的定制ROM可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品

相关推荐
奔跑中的蜗牛66619 小时前
一次播放器架构升级:Android 直播间 ANR 下降 60%
android
程序猿阿越19 小时前
Kafka4源码(二)创建Topic
java·后端·源码阅读
测试工坊21 小时前
Android 视频播放卡顿检测——帧率之外的第二战场
android
Kapaseker1 天前
一杯美式深入理解 data class
android·kotlin
鹏多多1 天前
Flutter使用screenshot进行截屏和截长图以及分享保存的全流程指南
android·前端·flutter
Carson带你学Android1 天前
OpenClaw移动端要来了?Android官宣AI原生支持App Functions
android
黄林晴1 天前
Android 删了 XML 预览,现在你必须学 Compose 了
android
三少爷的鞋1 天前
Android 面试系列 | 内存泄露:从"手动配对"到"架构自愈"
android
恋猫de小郭1 天前
什么 AI 写 Android 最好用?官方做了一个基准测试排名
android·前端·flutter
louisgeek1 天前
Android MediatorLiveData
android