Physical Address:
ChongQing,China.
WebSite:
在安卓的日常开发中,系统属性是会经常遇到并使用的。系统属性在作用上比较类似于安卓系统中的环境变量,但相对于环境变量,系统属性具备更多的功能特性以及更高的灵活性,本篇文章就Android12为例总结一下安卓系统属性的相关知识。
在我们了解与使用系统属性时,我们应该要先了解系统属性的定义,在进行自定义属性时遵循其定义规范。
属性名称原则上可以为任意字符串,不过Google针对属性的名称制定了一套规范,大家在制定属性时应尽可能遵守该规范:
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]
其中prefix字段可以分为三种:
1)使用“”,一般我们会将“”进行忽略,这种系统属性会在系统重新启动之后丢失,在单个运行周期内可读写
2)使用ro,该prefix表明该属性是read only,无法被在系统运行期间改变,重启仍会保留
3)使用persist,该prefix表明属性是断电保存的,也就是说重启(包含冷启动与热启动)不会导致属性丢失,在单个运行周期内可读写
接下来是group字段,其用于聚合相关属性,一般用android子系统的名称进行标识,如audio,camera,graphics,telephony等,我们应当借鉴SELinux配置system/sepolicy/private/property_contexts中对android各个常见子系统的定义,如下所示:
子系统 | 定义 |
蓝牙相关 | bluetooth |
内核相关 | boot |
编译相关 | build |
电话相关 | telephony |
音频相关 | audio |
图形相关 | graphics |
vold相关 | vold |
如果是新增的group字段,我们需要补齐对应的SELinux上下文定义,否则会在SELinux规则检查时报错,在permissive权限下出现avc报错,在enforcing权限下则可能会导致程序无法运行。
除了group,我们还可以通过subgroup进一步地进行限定,一个group下可以拥有多个subgroup,定义subgroup应尽量避免重复字段或含义重复。
接下来是name和type,其中name 用于标识群组中的属性具体含义,属于高度概括的字段,而type 是一个可选元素,用于阐明属性name所对应的类型或 intent。对于type的使用,并没有严格的规定,不过官方仍旧给出了一些使用参考:
enabled:如果类型是用于启用或停用功能的布尔值系统属性,请使用此类型。
config:如果 intent 是为了阐明系统属性不代表系统的动态状态,请使用此类型;它表示一个预配置的值(例如只读对象)。
List:如果系统属性的值为列表,请使用此类型。
Timeoutmillis:如果是超时值(以毫秒为单位)的系统属性,请使用此类型
这里讲的预设系统属性,是指我们在编译前进行设置,在编译构建之后这些预设的属性跟随镜像一起分发,在系统启动时自动进行设定的使用场景。这种场景也是较为常见的,尤其是当我们的运行程序需要依赖于系统属性的值做差异化处理时,我们往往都希望在编译构建前就能完成系统属性的差异化设定。
事实上,我们可以在编译前通过在makefile中设置变量来达到系统启动时自动设置环境变量,在Android12中所支持的变量如下:
PRODUCT_SYSTEM_PROPERTIES
PRODUCT_VENDOR_PROPERTIES
PRODUCT_ODM_PROPERTIES
PRODUCT_PRODUCT_PROPERTIES
这里给出一个使用的示例:
PRODUCT_SYSTEM_PROPERTIES += ro.launcher.blur.appLaunch=0
在构建之后,我们在系统启动之后就会看到ro.launcher.blur.appLaunch的值为0。
上述变量所定义的属性值最终都会汇聚到每个partion下的build.prop,init进程启动时会读取这些build.prop并进行设定,在Android12中所有的build.prop如下:
./product/etc/build.prop
./vendor_dlkm/etc/build.prop
./system/build.prop
./vendor/odm_dlkm/etc/build.prop
./vendor/odm/etc/build.prop
./vendor/build.prop
./system_ext/etc/build.prop
相关的执行逻辑源码位于system/core/init/property_service.cpp中,这里摘录其核心部分:
//system/core/init/property_service.cpp
void PropertyLoadBootDefaults() {
.....
LoadPropertiesFromSecondStageRes(&properties);
load_properties_from_file("/system/build.prop", nullptr, &properties);
load_properties_from_partition("system_ext", /* support_legacy_path_until */ 30);
// TODO(b/117892318): uncomment the following condition when vendor.imgs for aosp_* targets are
// all updated.
// if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_R__) {
load_properties_from_file("/vendor/default.prop", nullptr, &properties);
// }
load_properties_from_file("/vendor/build.prop", nullptr, &properties);
load_properties_from_file("/vendor_dlkm/etc/build.prop", nullptr, &properties);
load_properties_from_file("/odm_dlkm/etc/build.prop", nullptr, &properties);
load_properties_from_partition("odm", /* support_legacy_path_until */ 28);
load_properties_from_partition("product", /* support_legacy_path_until */ 30);
.....
property_initialize_ro_product_props();
property_initialize_build_id();
property_derive_build_fingerprint();
property_derive_legacy_build_fingerprint();
property_initialize_ro_cpu_abilist();
update_sys_usb_config();
}
在日常使用时,我们经常需要获取与设定系统属性。首先是从命令行进行获取或设置,这里我们会用到两个命令:getprop与setprop;前者用于获取属性,而后者用于设置系统属性。如下所示:
//getprop + grep
trout_x86:/ $ getprop | grep audio
[init.svc.audioserver]: [running]
[init.svc.vendor.audio-hal]: [running]
//setprop
trout_x86:/ $ setprop persist.evs.camera.mode 2
trout_x86:/ $ getprop | grep "evs.camera"
[persist.evs.camera.mode]: [2]
除了命令行,我们也经常需要在代码中对属性进行获取和设定,Android为此提供了快捷的API,包含C/C++与Java,如下示例:
//C++ binding,system/core/libcutils/include/cutils/properties.h
int property_get(const char* key, char* value, const char* default_value);
int8_t property_get_bool(const char *key, int8_t default_value);
int64_t property_get_int64(const char *key, int64_t default_value);
int32_t property_get_int32(const char *key, int32_t default_value);
//属性设置
int property_set(const char *key, const char *value);
//Java binding,frameworks/base/core/java/android/os/SystemProperties.java
public static String get(@NonNull String key) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
public static String get(@NonNull String key, @Nullable String def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key, def);
}
public static int getInt(@NonNull String key, int def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_int(key, def);
}
public static long getLong(@NonNull String key, long def) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_long(key, def);
}
//设置属性
public static void set(@NonNull String key, @Nullable String val) {
if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("value of system property '" + key
+ "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
}
if (TRACK_KEY_ACCESS) onKeyAccess(key);
native_set(key, val);
}
//属性变化添加回调
public static void addChangeCallback(@NonNull Runnable callback) {
synchronized (sChangeCallbacks) {
if (sChangeCallbacks.size() == 0) {
native_add_change_callback();
}
sChangeCallbacks.add(callback);
}
}
有些时候我们需要在rc中通过属性值的变化来启动一些程序,这时候就需要在rc中设定相应的Actions来达成这一目的。这里给出一个示例:
on property:persist.automotive.evs.mode=0
# stop EVS and automotive display services
stop automotive_display
stop evs_sample_driver
stop evs_manager
stop evs_app
在该示例中,当属性persist.automotive.evs.mode值为0时,即停止service:automotive_display、evs_sample_driver、evs_manager、evs_app。