Android 系统作为一套复杂的开源操作系统,提供了丰富的功能接口满足开发者的需求。但为了维护系统稳定性、安全性和向后兼容性,从Android9开始,官方对部分功能接口进行了隐藏。这些隐藏 API(通过 @hide
标注)虽然在源码中存在,但被排除在官方 SDK 的公共接口之外。开发者可以通过一些技术手段调用隐藏 API,但这可能导致兼容性问题,甚至触发系统的访问限制。
本文将详细解析隐藏 API 的定义、分类及其访问限制,并从源码角度揭示隐藏 API 的生成、检测和调用方式。同时,也会探讨 greylist
和 blacklist
策略,以及开发者在面对隐藏 API 时的解决方案。
背景
什么是隐藏 API?
隐藏 API 是 Android 系统中不对第三方开发者公开的方法或字段,通常在源代码中通过 @hide
注解标识。这些 API 封装了底层实现细节、硬件访问功能或敏感的系统操作,旨在限制开发者对系统内部的直接依赖。
隐藏 API 的设计初衷
隐藏 API 的存在主要基于以下考虑:
- 系统稳定性:隐藏可能会因系统升级而发生变化的接口,防止开发者依赖非稳定的实现。
- 安全性:防止敏感功能被滥用,例如直接操作硬件或访问底层资源。
- 兼容性:减少系统接口的碎片化,避免设备厂商或开发者通过非公开方法破坏统一性。
- 开发灵活性:为系统内部功能保留灵活调整的空间,不受外部依赖的约束。
隐藏 API 的现状
从 Android 9 (Pie) 开始,Google 引入了隐藏 API 执行策略(Hidden API Enforcement Policy),对隐藏 API 的访问进行了更严格的限制,普通应用只能访问特定的灰名单 API,而黑名单 API 则完全禁止。
隐藏 API 的分类与访问策略
隐藏 API 的分类
Google 根据隐藏 API 的用途和敏感性,制定了如下分类规则:
分类 | 访问权限 | 适用范围 |
---|---|---|
Public | 公开 API,无限制。 | 普通开发者可以访问,包含在 Android SDK 中。 |
Whitelist | 白名单 API,通常无限制。 | 系统公开的基础接口,官方鼓励开发者使用。 |
Light Greylist | 灰名单 API,普通应用访问时触发警告日志。 | 系统为了兼容性暂时保留的旧 API,可能在未来版本中移除。 |
Dark Greylist | 深灰名单 API,仅限系统应用访问。 | 对普通应用隐藏,但系统级应用仍可以使用(如预装的厂商应用)。 |
Blacklist | 黑名单 API,完全禁止访问。 | 涉及底层安全性或敏感操作的 API,调用时会抛出 NoSuchMethodError 或 IllegalAccessError 。 |
Core platform | 核心平台 API,仅允许 Platform 签名的应用访问。 | 与系统底层运行密切相关的 API,例如核心服务或硬件操作。 |
隐藏 API 的访问限制
Android 系统通过两种主要机制限制对隐藏 API 的访问:
(1) Hidden API Enforcement
Android 9 开始,系统引入了隐藏 API 执行策略,默认情况下禁止普通应用直接访问隐藏 API。如果尝试访问隐藏 API,会根据 API 的分类触发不同的行为:
- 灰名单 API:运行时记录警告日志,但允许访问。
- 黑名单 API:直接抛出异常,应用运行失败。
(2) 签名权限限制
隐藏 API 的访问权限还与应用的签名关系密切。根据应用签名的不同,访问权限分为以下几种:
- Platform 签名:
- 最高权限,允许访问所有隐藏 API。
- 使用系统的
Platform Key
签名,例如framework.jar
。
- 系统签名:
- 允许访问灰名单 API,部分黑名单 API 被限制。
- 使用系统密钥签名(非 Platform Key)。
- 普通签名:
- 仅能访问白名单和少量灰名单 API。
- 大部分黑名单和深灰名单 API 被限制。
隐藏 API 的生成与源码分析
@hide
注解的作用
在 Android 源码中,方法或字段可以通过 @hide
注解标记为隐藏。标记后,这些 API 在 SDK 编译时会被剔除,开发者无法直接通过 SDK 调用。
如:
/**
* @hide
*/
public static String get(String key) {
return native_get(key);
}
Hidden API 配置文件
隐藏 API 的分类信息在ROM编译过程中由配置文件控制,这些文件位于源码的以下路径:
hiddenapi-light-greylist.txt
:轻灰名单。hiddenapi-dark-greylist.txt
:深灰名单。hiddenapi-blacklist.txt
:黑名单。
在这些文件中,每一行都记录了一个方法或字段的完整描述,如:
Landroid/os/SystemProperties;->get(Ljava/lang/String;)Ljava/lang/String;,light-greylist
Hidden API 的运行时行为
Android 系统在运行时根据上述分类文件判断应用对隐藏 API 的访问权限。如果尝试访问超出权限范围的 API,会触发以下行为:
- 记录警告日志: Accessing hidden method Landroid/os/SystemProperties;->get(Ljava/lang/String;)Ljava/lang/String; (greylist, linking, allowed)
- 甚至是抛出异常: java.lang.NoSuchMethodError...
如何应对
作为开发者,我们应当积极响应官方的要求,同时也是让我们的App更加健壮,尽量避免使用隐藏API。
检测隐藏 API 的调用
-
Hidden API 日志:
- 在
logcat
中查看隐藏 API 的访问记录。 - 示例: Accessing hidden method Landroid/os/SystemProperties;->get(Ljava/lang/String;) (blacklist, blocked)
- 在
-
Hidden API Flags 文件:
- 分析
hiddenapi-flags.csv
文件,确认某方法的分类。 - 示例: Landroid/app/ActivityManager;->getService()Landroid/app/IActivityManager;,blacklist
- 分析
-
还可以利用
StrictMode
API 来测试App是否使用了非 SDK 接口。使用detectNonSdkApiUsage
方法来启用此 API。启用StrictMode
API 后,可以使用penaltyListener
来接收每次使用隐藏接口时触发的回调。
如何调用隐藏 API
如果实在有必要,也可以有一些黑科技可以调用到Hide的API
临时方案
开发过程中可以用下面命令解除限制:
adb shell settings put global hidden_api_policy_pre_p_apps 1
adb shell settings put global hidden_api_policy_p_apps 1
几个值含义如下:
- 0: Disable all detection of non-SDK interfaces. Using this setting disables all log messages for non-SDK interface usage and prevents you from testing your app using the StrictMode API. This setting is not recommended.
- 1: Enable access to all non-SDK interfaces, but print log messages with warnings for any non-SDK interface usage. Using this setting also allows you to test your app using the StrictMode API.
- 2: Disallow usage of non-SDK interfaces that belong to either the blacklist or the greylist and are restricted for your target API level.
- 3: Disallow usage of non-SDK interfaces that belong to the blacklist, but allow usage of interfaces that belong to the greylist and are restricted for your target API level.
RestrictionBypass
原理介绍:ANDROID API RESTRICTION BYPASS FOR ALL ANDROID VERSIONS
简单说就是让系统判断反射调用是来自系统而不是APP。因为安卓源码是通过回溯调用栈,通过调用者的Class来判断是否是系统代码的调用(所有系统的代码都通过BootClassLoader加载,判断ClassLoader即可)。RestrictionBypass库的做法是通过在jni层新建个线程,在这个线程里去反射,去除掉了java调用的信息,从而让安卓系统以为这个是系统调用。
FreeReflection
原理:借助系统的类去反射
- 我们通过反射 API 拿到 getDeclaredMethod 方法。getDeclaredMethod 是 public 的,不存在问题;这个通过反射拿到的方法我们称之为元反射方法。
- 我们通过刚刚反射拿到元反射方法去反射调用 getDeclardMethod。这里我们就实现了以系统身份去反射的目的——反射相关的 API 都是系统类,因此我们的元反射方法也是被系统类加载的方法;所以我们的元反射方法调用的 getDeclardMethod 会被认为是系统调用的,可以反射任意的方法。
如:
Method metaGetDeclaredMethod =
Class.class.getDeclaredMethod("getDeclardMethod"); // 公开API,无问题
Method hiddenMethod = metaGetDeclaredMethod.invoke(hiddenClass,
"hiddenMethod", "hiddenMethod参数列表"); // 系统类通过反射使用隐藏 API,检查直接通过。
hiddenMethod.invoke // 正确找到 Method 直接反射调用
按照这个思路,我们反射setHiddenApiExemptions
方法,这个方法在dalvik/system/VMRuntime.java
中,可以解除访问限制。
一个优化:apk正常的类加载的loader是PathClassLpader,这在高版本安卓系统上可能会失败。因为系统的类的loader都是BootClassLoader,所以将反射的代码的loader改成BootClassLoader,那么成功率会大幅提高。
- 将源代码打包成dex文件
- 将dex内容读出来并通过Base64转码,hardcode写到代码里
- 将上面的代码decode出来,写入到文件中
- 通过DexFile加载上面的文件
- 通过loadClass加载class,并调用对应的方法
BypassHiddenApiRestriction
其原理简单说就是调用mirror::Class::FindClassMethod
函数获取到方法对应的ArtMethod指针,这个指针就是此方法的jmethodId。调用CallObjectMethod并传入这个jmethodId便可实现非SDK接口的调用。其文档中有详细的探究和解释,感兴趣可以仔细看看。
系统厂商
最后甚至可以寻求厂商给App加白名单……
总结
隐藏 API 是 Android 系统为维护稳定性、安全性和兼容性而设计的重要机制。虽然开发者可以通过技术手段调用隐藏 API,但在实际开发中,避免依赖隐藏 API 是最安全的选择。对于需要访问隐藏功能的场景,应优先考虑公开 API 或与设备厂商合作获取支持。
在未来,随着 Android 系统的不断升级,Google 对隐藏 API 的限制会更加严格,这提醒开发者始终遵循官方规范,以确保应用的长期兼容性和稳定性。
最近的官方对其命名和行为有一些调整,但其本质基本不变。见官方最新文档 developer.android.com/guide/app-c…