Fork me on GitHub

[Android][Framework]SystemProperties

属性简介

在Android 系统中,为统一管理系统的属性,设计了一个统一的属性系统。每个属性都有一个名称和值,他们都是字符串格式。属性被大量使用在Android系统中,用来记录系统设置或进程之间的信息交换。属性是在整个系统中全局可见的。每个进程可以get/set属性。在编译的过程中会将各种系统参数汇总到build.prop 以及default.prop 这两个文件中,主要属性集中在build.prop中。

系统在开机后将读取配置信息并构建共享缓冲区,加快查询速度。另外一个方面,SettingsProvider会在系统第一次初始化时(刷机第一次启动)后,将从Defaults.xml中读取数据然后写入数据库Settings.db 目录。并构建一个缓冲系统供其他应用查询。下面将详细讲述。

属性类型

系统属性根据不同的应用类型,分为:

  • 不可变型

    属性名称以“ro.”开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。

  • 持久型

    属性名称以“persist.”开头,当设置这个属性时,其值也将写入/data/property。

  • 网络型

    属性名称以“net.”开头,当设置这个属性时,“net.change”属性将会自动设置,以加入到最后修改的属性名。(这是很巧妙的。 netresolve模块的使用这个属性来追踪在net.*属性上的任何变化。)

  • 启动和停止服务

    属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止服务。每一项服务必须在/init.rc中定义.系统启动时,与init守护进程将解析init.rc和启动属性服务。一旦收到设置“ ctrl.start ”属性的请求,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入“ init.svc.<服务名>“属性中 。客户端应用程序可以轮询那个属性值,以确定结果。

源码流程

首先,关于属性,是有长度定义的:

Bionic/libc/include/sys/system_properties.h

#define PROP_NAME_MAX	32
#define PROP_VALUE_MAX 92

即属性名长度最大32字节,属性值长度最大92字节。

如果把属性值修改超出最大长度,会报错:

error: ro.product.model cannot exceed 91 bytes: xxxxxxxxxxxxx...xxxxxxxxx

在系统初始化过程中,Android系统会分配一块共享内存用来存储properties。这些是由init守护进程完成的,其源代码位于:system/core/initinit守护进程将启动一个属性服务。

属性服务在init守护进程中运行。每一个客户端想要设置属性时,必须连接属性服务,再向其发送信息。属性服务将会在共享内存区中修改和创建属性。客户端想获得属性信息,可以从共享内存直接读取。这提高了读取性能。

// system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (!is_first_stage) {
property_init();
}
....
property_load_boot_defaults();
...
start_property_service();
}

看具体调用

// system/core/init/property_service.cpp

void property_init() {
if (__system_property_area_init()) { // 分配内存
ERROR("Failed to initialize property area\n");
exit(1);
}
}

void property_load_boot_defaults() {
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties_from_file(const char* filename, const char* filter) {
Timer t;
std::string data;
if (read_file(filename, &data)) {
data.push_back('\n');
load_properties(&data[0], filter);
}
NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration());
}

在加载默认属性的时候property_load_boot_defaults,读取的PROP_PATH_RAMDISK_DEFAULT来自于

// bionic/libc/include/sys/_system_properties.h
/* 旧版本 */
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"

/* 新版本 N+ */
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_VENDOR_BUILD "/vendor/build.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#define PROP_PATH_FACTORY "/factory/factory.prop"

在builtins.cpp中会从系统文件中读取默认的属性,并写入共享内存中。相同的属性会被后读入的属性替换。

// system/core/init/property_service.cpp
void load_system_props() {
load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
load_recovery_id_prop();
}

再看上层如何访问属性的。

// SystemProperties.java 定义了get和set方法
private static native String native_get(String key);
private static native String native_get(String key, String def);
/**
* Get the value for the given key.
* @return an empty string if the key isn't found
* @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static String get(String key) {
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
return native_get(key);
}

/**
* Get the value for the given key.
* @return if the key isn't found, return def if it isn't null, or an empty string otherwise
* @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static String get(String key, String def) {
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
return native_get(key, def);
}

该接口类在初始化运行环境中注册对应的cpp接口android_os_SystemProperties.cpp,实际操作通过JNI调用的是cpp文件对应的接口:

// frameworks/base/core/jni/AndroidRuntime.cpp
namespace android {
extern int register_android_os_SystemProperties(JNIEnv *env);
}
// frameworks/base/core/jni/android_os_SystemProperties.cpp
static void SystemProperties_set(JNIEnv *env, jobject clazz, jstring keyJ, jstring valJ)
{
int err;
const char* key;
const char* val;
key = env->GetStringUTFChars(keyJ, NULL);
if (valJ == NULL) {
val = "";       /* NULL pointer not allowed here */
else {
val = env->GetStringUTFChars(valJ, NULL);
}
err = property_set(key, val);
env->ReleaseStringUTFChars(keyJ, key);        
if (valJ != NULL) {
env->ReleaseStringUTFChars(valJ, val);
}
}

设置key的value时,需要作鉴权,根据设置程序所在进程的fd获知uid值,比如system server进程可以设置net打头的key,不可以设置gsm打头的key,相关的定义如下:

system/core/include/private/android_filesystem_config.h
#define AID_ROOT             0  /* traditional unix root user */
#define AID_SYSTEM        1000  /* system server */
#define AID_RADIO         1001  /* telephony subsystem, RIL */
#define AID_DHCP          1014  /* dhcp client */
#define AID_SHELL         2000  /* adb and debug shell user */
#define AID_CACHE         2001  /* cache access */
#define AID_APP          10000 /* first app user */

通过查看property_service.c,我们可以明确以下事实:

1、 属性名不是随意取的。在property_perms数组中定义了当前系统上可用的所有属性的前缀,以及相对应的存取权限UID。对属性的设置要满足权限要求,同时命名也要在这些定义的范围内。

2、 PA_COUNT_MAX指定了系统(共享内存区域中)最多能存储多少个属性。

这一段可以从property_set_impl方法逻辑看property前缀

/* White list of permissions for setting property services. */
struct {
const char *prefix;
unsigned int uid;
unsigned int gid;
} property_perms[] = {
{ "net.rmnet0.", AID_RADIO, 0 },
{ "net.gprs.", AID_RADIO, 0 },
{ "net.ppp", AID_RADIO, 0 },
{ "net.qmi", AID_RADIO, 0 },
{ "net.lte", AID_RADIO, 0 },
{ "net.cdma", AID_RADIO, 0 },
{ "ril.", AID_RADIO, 0 },
{ "gsm.", AID_RADIO, 0 },
{ "persist.radio", AID_RADIO, 0 },
{ "net.dns", AID_RADIO, 0 },
{ "sys.usb.config", AID_RADIO, 0 },
{ "net.", AID_SYSTEM, 0 },
{ "dev.", AID_SYSTEM, 0 },
{ "runtime.", AID_SYSTEM, 0 },
{ "hw.", AID_SYSTEM, 0 },
{ "sys.", AID_SYSTEM, 0 },
{ "sys.powerctl", AID_SHELL, 0 },
{ "service.", AID_SYSTEM, 0 },
{ "wlan.", AID_SYSTEM, 0 },
{ "gps.", AID_GPS, 0 },
{ "bluetooth.", AID_BLUETOOTH, 0 },
{ "dhcp.", AID_SYSTEM, 0 },
{ "dhcp.", AID_DHCP, 0 },
{ "debug.", AID_SYSTEM, 0 },
{ "debug.", AID_SHELL, 0 },
{ "log.", AID_SHELL, 0 },
{ "service.adb.root", AID_SHELL, 0 },
{ "service.adb.tcp.port", AID_SHELL, 0 },
{ "persist.logd.size",AID_SYSTEM, 0 },
{ "persist.sys.", AID_SYSTEM, 0 },
{ "persist.service.", AID_SYSTEM, 0 },
{ "persist.security.", AID_SYSTEM, 0 },
{ "persist.gps.", AID_GPS, 0 },
{ "persist.service.bdroid.", AID_BLUETOOTH, 0 },
{ "selinux." , AID_SYSTEM, 0 },
{ "wc_transport.", AID_BLUETOOTH, AID_SYSTEM },
{ "build.fingerprint", AID_SYSTEM, 0 },
{ "partition." , AID_SYSTEM, 0},
#ifdef DOLBY_UDC
{ "dolby.audio", AID_MEDIA, 0 },
#endif // DOLBY_UDC
#ifdef DOLBY_DAP
// used for setting Dolby specific properties
{ "dolby.", AID_SYSTEM, 0 },
#endif // DOLBY_DAP
{ "sys.audio.init", AID_MEDIA, 0 },
{ NULL, 0, 0 }
};

在开机启动后的init操作中,会执行一个loop循环,当检测到有新的设置时,进入设置流程,鉴权失败会提示相关的异常,如sys_prop: permission denied uid:1000 name:gsm.phone.id

system/build.prop

system/build.prop文件

#
# ADDITIONAL_BUILD_PROPERTIES
#
...
dalvik.vm.heapminfree=6m
dalvik.vm.heapstartsize=14m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapmaxfree=8m
...

import /system/vendor/vendor.prop

#IMPORT REGIONALIZATION VENDOR PROP PATH LAST IN ORDER TO OVERRIDE PROPERTIES#
import /persist/speccfg/vendor_persist.prop

import /system/vendor/default.prop

import /system/vendor/power.prop

system/build.prop生成过程

从build.prop输出,从注释内容可以看到:

  1. 执行build/tools/buildinfo.sh
  2. 把device/qcom/msmxxxx/system.prop的内容拷贝到$(OUT_TARGET_DEVICE)/system/build.prop
  3. 将ADDITIONAL_BUILD_PROPERTIES也添加到$(OUT_TARGET_DEVICE)/system/build.prop

在system/build.prop添加自定义属性

  1. 在buildinfo.sh中添加自定义property
  2. 最简单的就是在system.prop里添加一行,然后编译会将其追加到目标文件的
  3. ADDITIONAL_BUILD_PROPERTIES 是MakeFile的一个声明,也就是在MakeFile中通过ADDITIONAL_BUILD_PROPERTIES += persist.sys.xxxx=1这种方式就可以添加自定义的属性。

和自定属性相关的实例

可以使用System Properties记录用户习惯。

比如,我的设备需要提供Wifi热点功能,当用户主动打开热点后,需要用一个属性记录用户习惯,当设备关机重启后,根据该属性自动打开热点。

所以首先创建一个persist属性,写在/device/平台/型号/system.prop文件最后。

persist.sys.hotspot.enable=off

然后在手动开关热点的时候,记录用户的操作到该属性中:

// ConnectivityManager.java
@SystemApi
public void startTethering(int type, boolean showProvisioningUi,
final OnStartTetheringCallback callback, Handler handler) {
...
if (type == ConnectivityManager.TETHERING_WIFI) {
SystemProperties.set("persist.sys.hotspot.enable", "on");
}
...
}

@SystemApi
public void stopTethering(int type) {
...
if (type == ConnectivityManager.TETHERING_WIFI) {
SystemProperties.set("persist.sys.hotspot.enable", "off");
}
...
}

最后在开机的时候根据记录的用户习惯,自动打开热点:

private void startWifiTether() {
String state = SystemProperties.get("persist.sys.hotspot.enable", "off");
if (TextUtils.equals(state, "on")) {
WifiManager wifimanager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
wifimanager.setWifiEnabled(false); // disable wifi when using wifi hotspot
wifimanager.setWifiApEnabled(null, true);
}
}

Ref

https://www.cnblogs.com/l2rf/p/6610348.html

https://www.cnblogs.com/Peter-Chen/p/3946129.html

https://blog.csdn.net/ameyume/article/details/8056492

坚持原创技术分享,您的支持将鼓励我继续创作!