0x00 起因

升级App的 targetSdkVersion至27之后,发现原有的一个获取 Android 系统 BuildProperties信息的类无法正常工作了,初始化时会抛出异常

1
2
3
4
5
6
java.io.FileNotFoundException: /system/build.prop (Permission denied)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:200)
at java.io.FileInputStream.<init>(FileInputStream.java:150)
at java.io.FileInputStream.<init>(FileInputStream.java:103)
at java.io.FileReader.<init>(FileReader.java:58)

在初始化BuildProperties时,会去读取/system/build.prop的文件信息,相关代码如下:

1
2
3
4
5
6
7
8
9
10

private BuildProperties() {
mProperties = new Properties();
try {

mProperties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
} catch (IOException e) {
e.printStackTrace();
}
}

因为在 Android O 开始,/system/build.prop的读取权限不再对非root用户开放,通过查看文件权限可以确认这个问题

  • Android 8.1手机
1
2
$ adb shell ls -l /system/build.prop
-rw------- 1 root root 4699 2009-01-01 08:00 build.prop

可以看到build.props的文件权限变成了600

  • Android 6.0手机
1
2
$ adb shell ls -l /system/build.prop
-rw-r--r-- root root 9018 2017-12-16 01:51 build.prop

此时的文件权限是644,非root用户也有读取权限。

所以这段代码之前可以正常工作,在Android O上就会抛异常。

0x01 解决方法

1. getprop

通过adb shell命令进入到Android手机的shell环境中,可以执行内置的命令getprop,这条命令返回的是Android property的一个合计,其相关说明为get property via the android property service,这些属性从多个文件中加载,包括/system/build.prop/default.prop,因此可以在代码中调用这条命令以此获取。

这样做有两个问题: 1是比直接读取/system/build.prop文件会多几个属性,因为getprop命令里还包含了default.prop文件中的信息,这个问题不大。2是终端输出的信息是形如:

1
2
3
4
5
6
[sys.lineage_settings_system_version]: [3]
[sys.logbootcomplete]: [1]
[sys.oem_unlock_allowed]: [1]
[sys.rescue_boot_count]: [1]
[sys.retaildemo.enabled]: [0]
[sys.sysctl.extra_free_kbytes]: [43200]

这样的信息,与直接读取文件相比,key 和 value 上被包裹了中括号,因此需要使用正则对结果进行处理之后才能使用。

相关代码如下:不完整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static BuildProperties getInstance() {
return InstanceHolder.mInstance;
}

private final Properties mProperties;
private final Pattern regex = Pattern.compile("\\[(.+)]: \\[(.+)]");
private BuildProperties() {
mProperties = new Properties();
try {
Process p = Runtime.getRuntime().exec("getprop");
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = in.readLine()) != null && !line.equals("null")) {
this.parseLine(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void parseLine(String line) {
if (StringUtil.isNotBlank(line)) {
Matcher m = regex.matcher(line);
if (m.find()) {
mProperties.setProperty(m.group(1), m.group(2));
}
}
}

2. 通过反射调用 SystemProperties 进行获取

Android系统源码中大量使用了android.os.SystemProperties进行系统属性的获取与设置,我们可以通过反射来获取这个类进行调用。

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SystemProperty {
private final Context mContext;

public SystemProperty(Context mContext) {
this.mContext = mContext;
}

public String getOrThrow(String key) throws NoSuchPropertyException {
try {
ClassLoader classLoader = mContext.getClassLoader();
Class SystemProperties = classLoader.loadClass("android.os.SystemProperties");
Method methodGet = SystemProperties.getMethod("get", String.class);
return (String) methodGet.invoke(SystemProperties, key);
} catch (ClassNotFoundException e) {
throw new NoSuchPropertyException(e);
} catch (NoSuchMethodException e) {
throw new NoSuchPropertyException(e);
} catch (InvocationTargetException e) {
throw new NoSuchPropertyException(e);
} catch (IllegalAccessException e) {
throw new NoSuchPropertyException(e);
}
}

public String get(String key) {
try {
return getOrThrow(key);
} catch (NoSuchPropertyException e) {
return null;
}
}

}

代码来自项目android-getprop