Android P 非 SDK 的隐藏 API 调用检测

为什么要检测?

Google 在 2018 年的 I/O 大会上发布了 Android P 的 Developer Preview 2(简称 DP2)版本,其中还说明了在以后的 Android P 上,将对非 SDK API 的调用进行限制。目前,开发者对于非 SDK API 的调用,只能采取反射或 JNI 间接调用的方法进行调用。由于 Android 是开源的,所以开发者对非公有 SDK 的调用十分混乱,Google 此举也是为了进一步防止碎片化,规范开发者的 API 使用行为。

Android P 引入非 SDK API 检测

而在运行 Android P 的手机上,启动 APP 时,ART 虚拟机就会对 APP 的 Dex 文件进行检测,比对内置的黑名单、暗灰名单、亮灰名单,一旦发现 APP 内含有上面名的中的调用,就会发出警告,严重的,会直接抛出异常,如果没有进行处理,将导致 APP 的 Crash,即使进行了 try-catch,这些反射调用的功能,也会使用不正常。

调用了黑名单(BlackList)中的 API,APP 在 Android P 上运行时抛出异常:

抛出异常

Google 官方给出的使用非 SDK API 的影响:

Android P 上使用非 SDK API 的影响

因此,开发者应该尽量避免使用反射调用非 SDK API,防止在 Android P 上出现问题,为了避免 APP 体验受到影响,兼容 Android P,我们需要对现有 APP 的 API 使用情况进行检测。

如何检测?

Google 官方并没有直接放出 APP 检测工具,可能是因为还处于开发者预览版的阶段,最终的黑白名单还没有完全确定下来,但可以通过 AOSP 的 master 源码编译后获取这个名为veridex的工具

AOSP 的获取方法这里不详细讨论,可以参考:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/

veridex 的编译方法

首先需要建立好 AOSP 的构建环境,可以使用 macOS 或者 Ubuntu,这里以 64 位的 Ubuntu 16.04.4 LTS 为例:

  1. 安装 OpenJDK 8,命令:sudo apt install openjdk-8-jdk

  2. 安装依赖的库和组件,命令:sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev libgl1-mesa-dev libxml2-utils xsltproc unzip

AOSP 根目录

接着进入 AOSP 的根目录(如上图所示),执行:make appcompat,如果机器性能较好,可以加上-j8或者其他数字(根据 CPU 线程数),来并行编译,从而加快编译速度

等待一会儿以后,出现如下内容即为编译完成(中间有任何 failed 的提示,或者最终进度不到 100%,都为失败):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
yuanguozheng@pc:~/aosp/aosp$ make appcompat -j32
============================================
PLATFORM_VERSION_CODENAME=Q
PLATFORM_VERSION=Q
TARGET_PRODUCT=aosp_arm
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a
TARGET_CPU_VARIANT=generic
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86

.....

DroidDoc took 11 sec. to write docs to out/soong/.intermediates/frameworks/base/hiddenapi-lists/android_common/docs/out
javadoc: option --boot-class-path not allowed with target 1.9
[100% 2601/2601] build out/target/common/obj/PACKAGING/hiddenapi-blacklist.txt
yuanguozheng@pc:~/aosp/aosp$

可以看到在out/target/common/obj/PACKAGING/目录下,生成了三个隐藏 API 的列表文件hiddenapi-blacklist.txt(黑名单)、hiddenapi-dark-greylist.txt(暗灰名单)、hiddenapi-light-greylist.txt(亮灰名单),这些即为 ART 检查 API 调用情况的依据

黑名单

此时 veridex 组件编译完成,接下来即可使用它对 apk 文件进行检测

API 调用情况检测

准备好需要检测的 APK,这里我们使用反射,获取一个 APP 内部类的私有成员变量值、调用一个私有方法,再使用反射调用 Android 的 Hidden API 中的值,进行举例,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 初始化一个 APP 中的类
AppInnerClass obj = new AppInnerClass();

ReflectionUtils.getFieldValue(obj, "mTestValue");
ReflectionUtils.invokeMethod(obj, "printTestValue", null, null);

// 反射调用 Android 私有 API
ReflectionUtils.getFieldValue("android.annotation.Dimension", null, "DP");
ReflectionUtils.getFieldValue("android.Manifest$permission", null, "BLUETOOTH_STACK");
}
}

Build 一个测试用的 APK 以后,进入到 AOSP 源码的根目录下(必须在 AOSP 源码根目录下),执行以下命令

1
./art/tools/veridex/appcompat.sh --dex-file=[待检测 apk 的路径]  --imprecise(这个参数可选,建议加上,更准确)

如下图所示,经过 veridex 工具检测的结果会在终端中输出,如果结果比较多,可以使用>>保存到一个文本文件中,需要时详细查看

veridex 检测结果

注意事项

目前,Android P 还处于 Developer Preview(DP2)的版本阶段,一些 API 和设计还不稳定,更多的 API 可能会被加入黑名单或者深灰名单,当然,某些 API 调用,也不排除被从名单中移除的可能,Google 在官方文档中,还说到不保证灰名单 API 可以正常调用,所以,如果被检测出来有 Hidden API 的使用,应对尽快修正

引述 Google 官方对灰名单的立场:

Greylisted non-SDK interfaces encompass methods and fields which continue to function in Android P, but to which we do not guarantee access in future versions of the platform. If there is a reason that you cannot implement an alternative strategy to a greylisted API, you may file a bug to request reconsideration of the restriction.

总结

随着 Android P 预览版的发布,众多的新特性也随之而来。从限制非 SDK 的隐藏 API 来看,可以体现出 Google 对 Android 平台进一步控制碎片化、规范开发行为的的决心,而开发者需要做的,则是不断地分析这些新特性和 API 调用变化,跟进 Google 的步伐,让 Android 平台 APP 体验越来越好!