专栏首页wOw的Android小站[Android][Framework]SystemProperties

[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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [Android][Framework] 全方位理解Android权限之底层实现概览

    这个阶段搞了很多和Android文件权限相关的问题,虽然一知半解,但也算是对Android权限机制有一些自己的理解。遂将这些内容整理出来。因为权限这部分涉及到的...

    wOw
  • [Android][Recovery]自动挂载system分区

    前一篇Recovery打开adb shell里提到system目录是用来挂载系统/system分区的,所以是一个空目录。这一点是通过打开adb shell后,查...

    wOw
  • [Android][Security] Android 逆向之 smali

    APK其实就是一个ZIP压缩包,将APK后缀改成ZIP后就可以解压出APK内部文件。

    wOw
  • safe-point(safepoint 安全点) 和 safe-region(安全区域)

    GC如何找到不可用的对象?编写代码的时候是可以知道对象不可用的,但对于程序来说,需要一定的方式来知晓,可用方法比如:编译分析,引用计数,和对象是否可达

    爬蜥
  • 获取JVM转储文件的Java工具类

    在上期文章如何获取JVM堆转储文件中,介绍了几种方法获取JVM的转储文件,其中编程方法是里面唯一一个从JVM内部获取的方法。这里就不演示了其他方法获取正在运行的...

    FunTester
  • Android简单的圆盘形菜单 博客分类: Android Android360

       今天偶然看到一个圆盘形的菜单,还可以转动,感觉挺有意思,然后想了想,做了个简单的效果。       思路是这样的,定一个原点和一个半径,圆的四周均匀...

    chroya
  • 第8期 | jsmn,一个资源占用极少的json解析器

    本专栏由Mculover666创建,主要内容为寻找嵌入式领域内的优质开源项目,一是帮助开发者使用开源项目实现更多的功能,二是通过这些开源项目,学习大佬的代码及背...

    Mculover666
  • 小朋友学经典算法(13):两数交换

    在学C语言的时候,学过两数交换:《小朋友学C语言(25):两数交换》 https://www.jianshu.com/p/64bc70f0abfe

    海天一树
  • 万字长文概述NLP中的深度学习技术

    自然语言处理(NLP)是指对人类语言进行自动分析和表示的计算技术,这种计算技术由一系列理论驱动。NLP 研究从打孔纸带和批处理的时代就开始发展,那时分析一个句子...

    机器之心
  • 干货 | 万字长文概述NLP中的深度学习技术

    自然语言处理(NLP)是指对人类语言进行自动分析和表示的计算技术,这种计算技术由一系列理论驱动。NLP 研究从打孔纸带和批处理的时代就开始发展,那时分析一个句子...

    zenRRan

扫码关注云+社区

领取腾讯云代金券