我们在androidP及之前的版本,平台侧及应用层开发习惯于通过调用(或者反射)SystemProperties系统API的方式进行系统属性的读写。
但是拿到Android R源代码对比后,你会发现,平台侧代码采用了一种将系统属性封装成类方法的形式供开发者调用(实际上Android Q上已经引入了这种方式,只不过笔者负责的源码中并为采用,所以也没有留意,这种方式在Android R上使用的更为普遍),替代了直接通过调用SystemProperties读写系统属性的方式。
下面通过工作中在Android R平台上添加系统属性时引发的疑问及思考,详细阐述sysprop及SystemProperties的关系,最后给出了添加系统属性的一种方法。
首先分别在a部分和b部分简单回顾下Android P及之前版本上普遍采用的存取系统属性的方式。
然后在c部分给出Android R上新的sysprop_library维护管理系统属性的方式。
我们常常通过以下的方式进行系统属性的读写:
以TelephonyManager.java为例,写入属性:
//form Pie 9.0.0_r3 frameworks/base/telephony/java/android/telephony/TelephonyManager.java
public static void setTelephonyProperty(String property, String value) {
if (value == null) {
value = "";
}
Rlog.d(TAG, "setTelephonyProperty: success" + " property=" +
property + " value: " + value);
SystemProperties.set(property, value);//调用系统API存储系统属性
}
通过上面的方式可以修改已经存在的系统属性,如果不存在则终端中会直接生成一个新的系统属性。
下面的代码片段是读取属性的方式:
public static String getTelephonyProperty(String property, String defaultVal) {
String propVal = SystemProperties.get(property);
return propVal == null ? defaultVal : propVal;
}
连接上手机通过adb命令adb shell getprop可以查看终端存储的属性:
F:\adb>adb shell getprop
[persist.radio.multisim.config]: [dsds]
... ...
通过调用系统API SystemProperties的方式,开发人员只需要明确了属性名就可以直接去读写或者自定义新的系统属性,非常方便。
但是便利的弊端就是不方便维护,无法做到统一管理。同时直接操作属性名称字符串也很容易编辑错误,虽然大家可以将属性名称放在一个工具类中进行维护,但是无法进行约束。
开发中有不少系统属性需要存储两个值,例如双卡终端中卡1和卡2的卡名称,源码中通过同一个属性名称进行存储,属性值存储时采用逗号“,”进行区分。如下:
F:\adb>adb shell getprop
//卡1插了张SRF的SIM卡,卡2插入的一张移动SIM卡
[gsm.sim.operator.alpha]: [SFR,中国移动]
下面贴一下这种存储两个值的系统属性的方式:
/**
* Sets a per-phone telephony property with the value specified.
*
* @hide
*/
public static void setTelephonyProperty(int phoneId, String property, String value) {
String propVal = "";
String p[] = null;
String prop = SystemProperties.get(property);
if (value == null) {
value = "";
}
if (prop != null) {
p = prop.split(",");
}
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
Rlog.d(TAG, "setTelephonyProperty: invalid phoneId=" + phoneId +
" property=" + property + " value: " + value + " prop=" + prop);
return;
}
for (int i = 0; i < phoneId; i++) {
String str = "";
if ((p != null) && (i < p.length)) {
str = p[i];
}
propVal = propVal + str + ",";
}
propVal = propVal + value;
if (p != null) {
for (int i = phoneId + 1; i < p.length; i++) {
propVal = propVal + "," + p[i];
}
}
if (propVal.length() > SystemProperties.PROP_VALUE_MAX) {
Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId +
" property=" + property + " value: " + value + " propVal=" + propVal);
return;
}
SystemProperties.set(property, propVal);
}
其中,上面的代码片段中参数phoneId就是代表的双卡终端(两个Phone)的phone的编号0和1。
代码逻辑很简单,就是将两个值通过逗号拼接后,再进行存储。
想必大家很容易就想到了如何去读取这种系统属性。通过逗号切割成数组并返回对应下标编号的值就可以了,如下:
/**
* Gets a per-phone telephony property.
*
* @hide
*/
public static String getTelephonyProperty(int phoneId, String property, String defaultVal) {
String propVal = null;
String prop = SystemProperties.get(property);
if ((prop != null) && (prop.length() > 0)) {
String values[] = prop.split(",");
if ((phoneId >= 0) && (phoneId < values.length) && (values[phoneId] != null)) {
propVal = values[phoneId];
}
}
return propVal == null ? defaultVal : propVal;
}
接下来,我们把关注点转移到Android R上。笔者在开发过程中,发现R源码中更多采用了如下的方式读写系统属性,我们以android.sysprop.TelephonyProperties(接下来就是对该类进行分析)为例进行分析。
单一值系统属性,分别以TelephonyManager中读取系统属性“persist.radio.multisim.config”和PhoneGlobals中更新系统属性"persist.radio.airplane_mode_on”为例:
import android.sysprop.TelephonyProperties;
//例子一
//TelephonyManager.java
/** {@hide} */
@UnsupportedAppUsage
public MultiSimVariants getMultiSimConfiguration() {
//读取属性[persist.radio.multisim.config]: [dsds]
String mSimConfig =
TelephonyProperties.multi_sim_config().orElse("");
if (mSimConfig.equals("dsds")) {
return MultiSimVariants.DSDS;
}
... ...
}
//例子二
packages/services/Telephony/src/com/android/phone/PhoneGlobals.java
private void setRadioPowerOn(Context context) {
Log.i(LOG_TAG, "Turning radio on - airplane");
... ...
//修改属性"persist.radio.airplane_mode_on"
TelephonyProperties.airplane_mode_on(false); // false means int value 0
... ...
}
多值(以二值为例)系统属性,以TelephonyManager中读写系统属性“gsm.sim.operator.alpha"为例:
import android.sysprop.TelephonyProperties;
//TelephonyManager.java
/**
* Returns the Service Provider Name (SPN).
*
* @hide
*/
@UnsupportedAppUsage
public String getSimOperatorNameForPhone(int phoneId) {
//获取系统属性“gsm.sim.operator.alpha"
return getTelephonyProperty(phoneId, TelephonyProperties.icc_operator_alpha(), "");
}
/**
* Gets a typed per-phone telephony property from a schematized list property.
*/
private static <T> T getTelephonyProperty(int phoneId, List<T> prop, T defaultValue) {
T ret = null;
if (phoneId >= 0 && phoneId < prop.size()) ret = prop.get(phoneId);
return ret != null ? ret : defaultValue;
}
/**
* Set TelephonyProperties.icc_operator_alpha for the given phone.
*
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void setSimOperatorNameForPhone(int phoneId, String name) {
if (SubscriptionManager.isValidPhoneId(phoneId)) {
//更新系统属性“gsm.sim.operator.alpha"
List<String> newList = updateTelephonyProperty(
TelephonyProperties.icc_operator_alpha(), phoneId, name);
TelephonyProperties.icc_operator_alpha(newList);
}
}
/**
* Inserts or updates a list property. Expands the list if its length is not enough.
*/
private static <T> List<T> updateTelephonyProperty(List<T> prop, int phoneId, T value) {
List<T> ret = new ArrayList<>(prop);
while (ret.size() <= phoneId) ret.add(null);
ret.set(phoneId, value);
return ret;
}
可以看出,上面通过调用android.sysprop.TelephonyProperties中的相关API进行系统属性的读取及更新。还有一个明显的变化是,针对存储二值的系统属性,在存取的过程中采用了List列表的形式维护多个值。
看到这里,我们不禁要问,TelephonyProperties中的方法与系统属性是什么关系呢?或者说,TelephonyProperties类是怎样定义的呢,以方便我们调用相关方法,不然断然不知道哪个方法是干什么用的,或者是如何传参调用。
在源码中搜索你会发现,android.sysprop.TelephonyProperties.java类在AndroidR中并没有直接定义。这就很奇怪了?
我们接下来尝试搜索调用的TelephonyProperties中的方法名称,发现了文件AndroidR/system/libsysprop/srcs/android/sysprop/TelephonyProperties.sysprop
中对这些API的定义:
//TelephonyProperties.sysprop
# Copyright (C) 2019 The Android Open Source Project
... ...
module: "android.sysprop.TelephonyProperties"
owner: Platform
prop {
api_name: "airplane_mode_on"
type: Boolean
scope: Internal
access: ReadWrite
prop_name: "persist.radio.airplane_mode_on"
integer_as_bool : true
}
... ...
#
# The MCC+MNC (mobile country code+mobile network code) of the provider
# of the SIM. 5 or 6 decimal digits. Indexed by phone ID
#
# Availability: SIM state must be READY
#
prop {
api_name: "icc_operator_numeric"
type: StringList
scope: Internal
access: ReadWrite
prop_name: "gsm.sim.operator.numeric"
}
#
# also known as the SPN, or Service Provider Name. Indexed by phone ID
#
# Availability: SIM state must be "READY"
#
prop {
api_name: "icc_operator_alpha"
type: StringList
scope: Internal
access: ReadWrite
prop_name: "gsm.sim.operator.alpha"
}
#
# Property to set multi sim feature.
#
prop {
api_name: "multi_sim_config"
type: String
scope: Public
access: ReadWrite
prop_name: "persist.radio.multisim.config"
}
... ...
进入该路劲下/system/libsysprop/srcs/android/sysprop/
,发现这里面有很多l文件,文件后缀均为sysprop。
/system/libsysprop/srcs/android/sysprop目录下的文件:
AdbProperties.sysprop ContactsProperties.sysprop HdmiProperties.sysprop PowerProperties.sysprop TraceProperties.sysprop WifiProperties.sysprop
ApkVerityProperties.sysprop CryptoProperties.sysprop MediaProperties.sysprop SetupWizardProperties.sysprop VndkProperties.sysprop
CarProperties.sysprop DisplayProperties.sysprop OtaProperties.sysprop TelephonyProperties.sysprop VoldProperties.sysprop
该路径是在AndroidQ上首先创建定义的,但是TelephonyProperties.sysprop文件是在AndroidR上新添加的。(由于笔者参与TelephonyProperties的维护工作,继而才发现并尝试研究androidR上这种sysprop维护管理系统属性的方式。)
后面我们会知道这些文件是用来定义系统属性对应的api,把系统属性在此统一维护,开发人员只要调用对应的api即完成了对目标系统属性的访问。
但是,由于sysprop文件并不是java语言中的合法文件格式,所以我们猜想,谷歌在这里做了一层封装,只要按照sysprop文件的格式去声明使用系统属性,就会自动生成java语言中的类文件。
我们的猜想到底正不正确呢?只有我们找到sysprop对应的类文件,才能应证这种想法。
我们刚刚找到了TelephonyProperties.sysprop,先不着急验证我们上面的想法(去找对应的类文件),我们打算想办法先看看TelephonyProperties中的API都是怎样定义的,对这种API的定义加深下了解。
能够想到的是,我们可以先编译下该模块,然后解压释放出来.class文件,反编译看下该sysprop对应的java类是怎样的,这样便能够加深对sysprop的了解和调用。后面再仔细查找验证下sysprop在系统源码编译过程中是不是编译工具真的会将其转换成java类文件。
我们查看下该模块的定义/system/libsysprop/srcs/Android.bp:
system/libsysprop/srcs/Android.bp
sysprop_library {
name: "PlatformProperties",
srcs: ["**/*.sysprop"],
property_owner: "Platform",
api_packages: ["android.sysprop"],
apex_available: [
"//apex_available:platform",
"com.android.art.release",
"com.android.art.debug",
],
}
可以看到库名称为PlatformProperties。由于我本地代码并没有全部编译,首先使用下面的模块编译命令单独编译一下(可以加-j8/j16加快编译速度):
make PlatformProperties
编译完成后,输出该模块对应的jar包:
[100% 413/413] Copy: out/target/product/PorjectAndroidR/obj/JAVA_LIBRARIES/PlatformProperties_intermediates/javalib.jar
#### build completed successfully (35:08 (mm:ss)) ####
生成的javalib.jar其实就是PlatformProperties.jar,可以到对应目录中找到这个生成的库javalib.jar。
当然也可以在下面的目录中找到PlatformProperties.jar,它俩是一样的:
out\soong\.intermediates\system\libsysprop\srcs\PlatformProperties\android_common\javac\PlatformProperties.jar
将该jar包后缀修改为“.zip”并解压,可以看到里面有很多的.class字节码文件,我们仍然以TelephonyProperties.class文件为例分析。
将该字节码文件进行反编译,或者直接拖拽到AndroidStdio中查看。这里我们偷个懒,直接拖拽到AndroidStdio工具中查看:
PlatformProperties.jar解压后TelephonyProperties.class二进制码放在AndroidStdio中查看:
//The Software includes decompiling functionality ("JetBrains Decompiler")
//that enables reproducing source code from the original binary code.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package android.sysprop;
import android.os.SystemProperties;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.function.Function;
public final class TelephonyProperties {
private TelephonyProperties() {
}
private static Boolean tryParseBoolean(String str) {
String var1 = str.toLowerCase(Locale.US);
byte var2 = -1;
switch(var1.hashCode()) {
case 48:
if (var1.equals("0")) {
var2 = 2;
}
break;
case 49:
if (var1.equals("1")) {
var2 = 0;
}
break;
case 3569038:
if (var1.equals("true")) {
var2 = 1;
}
break;
case 97196323:
if (var1.equals("false")) {
var2 = 3;
}
}
switch(var2) {
case 0:
case 1:
return Boolean.TRUE;
case 2:
case 3:
return Boolean.FALSE;
default:
return null;
}
}
private static Integer tryParseInteger(String str) {
try {
return Integer.valueOf(str);
} catch (NumberFormatException var2) {
return null;
}
}
private static Long tryParseLong(String str) {
try {
return Long.valueOf(str);
} catch (NumberFormatException var2) {
return null;
}
}
private static Double tryParseDouble(String str) {
try {
return Double.valueOf(str);
} catch (NumberFormatException var2) {
return null;
}
}
private static String tryParseString(String str) {
return "".equals(str) ? null : str;
}
private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) {
try {
return Enum.valueOf(enumType, str.toUpperCase(Locale.US));
} catch (IllegalArgumentException var3) {
return null;
}
}
private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) {
if ("".equals(str)) {
return new ArrayList();
} else {
List<T> ret = new ArrayList();
int p = 0;
while(true) {
StringBuilder sb = new StringBuilder();
while(p < str.length() && str.charAt(p) != ',') {
if (str.charAt(p) == '\\') {
++p;
}
if (p == str.length()) {
break;
}
sb.append(str.charAt(p++));
}
ret.add(elementParser.apply(sb.toString()));
if (p == str.length()) {
return ret;
}
++p;
}
}
}
private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) {
if ("".equals(str)) {
return new ArrayList();
} else {
List<T> ret = new ArrayList();
String[] var3 = str.split(",");
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
String element = var3[var5];
ret.add(tryParseEnum(enumType, element));
}
return ret;
}
}
private static String escape(String str) {
return str.replaceAll("([\\\\,])", "\\\\$1");
}
private static <T> String formatList(List<T> list) {
StringJoiner joiner = new StringJoiner(",");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
T element = var2.next();
joiner.add(element == null ? "" : escape(element.toString()));
}
return joiner.toString();
}
private static <T extends Enum<T>> String formatEnumList(List<T> list, Function<T, String> elementFormatter) {
StringJoiner joiner = new StringJoiner(",");
Iterator var3 = list.iterator();
while(var3.hasNext()) {
T element = (Enum)var3.next();
joiner.add((CharSequence)(element == null ? "" : (CharSequence)elementFormatter.apply(element)));
}
return joiner.toString();
}
public static Optional<Boolean> airplane_mode_on() {
String value = SystemProperties.get("persist.radio.airplane_mode_on");
return Optional.ofNullable(tryParseBoolean(value));
}
public static void airplane_mode_on(Boolean value) {
SystemProperties.set("persist.radio.airplane_mode_on", value == null ? "" : (value ? "1" : "0"));
}
... ...
public static List<String> icc_operator_numeric() {
String value = SystemProperties.get("gsm.sim.operator.numeric");
return tryParseList((v) -> {
return tryParseString(v);
}, value);
}
public static void icc_operator_numeric(List<String> value) {
SystemProperties.set("gsm.sim.operator.numeric", value == null ? "" : formatList(value));
}
public static List<String> icc_operator_alpha() {
String value = SystemProperties.get("gsm.sim.operator.alpha");
return tryParseList((v) -> {
return tryParseString(v);
}, value);
}
public static void icc_operator_alpha(List<String> value) {
SystemProperties.set("gsm.sim.operator.alpha", value == null ? "" : formatList(value));
}
public static Optional<String> multi_sim_config() {
String value = SystemProperties.get("persist.radio.multisim.config");
return Optional.ofNullable(tryParseString(value));
}
public static void multi_sim_config(String value) {
SystemProperties.set("persist.radio.multisim.config", value == null ? "" : value.toString());
}
... ...
}
看到这里,大家应该就恍然大悟了。从这个反编译出来的文件可以看到,实际上就是对SystemProperties做了一层封装。
底层仍然通过“属性名-值”的方式进行存储。在处理存储多值(二值)的系统属性时,先维护一个list列表存储多个值,然后将list列表转换成以逗号间隔的字符串再进行最后的值存储。
到这里篇幅已然不短,小结一下:
上面简述了Android P及以前版本通过SystemProperties存取系统属性的方式;然后引出了Android R上存取系统属性的方式,并介绍了通过sysprop文件统一维护管理系统属性(或者叫平台属性)的方式。
由于篇幅过长,在下篇文章我们验证下“sysprop文件在编译时会转换成java类文件”的猜想,并简述下如何在sysprop中添加自定义属性。
请您移步《AndroidR 11 系统属性sysprop_library研究及其与SystemProperties的关系(二)》继续阅读。