在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/init
。 init
守护进程将启动一个属性服务。
属性服务在 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
# # 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
从build.prop输出,从注释内容可以看到:
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