Android14 新增系统服务后,应用调用出现 "hidden api" 警告的原因与解决方案
在 AOSP 开发中,我们经常需要新增一个系统服务,并让某些系统应用或特权应用去调用这个服务提供的接口。然而,当你写好服务端代码、编译、刷机,并在应用中尝试调用时,可能会在 logcat 中看到类似下面的警告日志:
Accessing hidden method Lcom/example/MyService;->doSomething()V (dark greylist, reflection)
这表示你的调用行为触发了 Android 的隐藏 API 限制,虽然目前可能只是警告,但在未来 Android 版本中可能会直接抛出异常导致应用崩溃。
为什么会出现这个警告?
Android 系统从 API Level 28(Android 9.0)开始引入了隐藏 API 限制机制 。凡是不在 public API 范围内的 Java 方法、字段或类,都会被系统视为"隐藏 API",并按照其访问危险程度分为以下几种名单(greylist / blacklist / whitelist 等):
- 灰名单 (greylist):允许访问,但会有警告日志。
- 黑名单 (blacklist):禁止访问,直接抛出异常。
- 白名单 (whitelist):系统或特定应用允许使用的隐藏 API。
当你在 AOSP 中新增一个系统服务 ,并在该服务中定义了一个新的方法(例如 MyService.doWork()),默认情况下这个 API 并不是官方公开 SDK 的一部分。因此,即使你编译出了整个系统,任何试图通过反射或直接调用(如果应用和系统服务在同一个进程)来使用这个新 API 的行为,都会被隐藏 API 机制拦截并记录警告。
如何解决?
针对这类"新增 API 只为特定应用提供"的场景,通常有两种解决方式:
方案一:使用 @SystemApi 注解
在你的系统服务的 Java 接口定义中,给方法或类加上 @SystemApi 注解:
java
/**
* 系统私用 API,仅供特定系统组件调用。
* @hide
*/
@SystemApi
public void doWork() {
// ...
}
同时,你需要将 API 加入到系统的 current.txt 或 api/ 目录中,让编译系统识别这是一个"系统公开 API"。这种做法适用于多系统模块或应用需要调用的 API,因为它会让 API 正式成为 SDK 的一部分(但仅限于系统映像)。
缺点:
- 流程较复杂,需要修改
api/文件并经过兼容性检查。 - 任何系统应用都能调用(虽然有
@SystemApi权限限制),不够精细。
方案二:通过隐藏 API 白名单文件(推荐)
Android 提供了一个专门用来豁免特定应用对隐藏 API 访问的机制:hiddenapi-package-whitelist.xml。
该文件位于 AOSP 源码中的:
frameworks/base/data/etc/hiddenapi-package-whitelist.xml
你只需要将需要调用新 API 的应用包名添加到这个 XML 文件中即可。
示例内容:
xml
<?xml version="1.0" encoding="utf-8"?>
<whitelist>
<!-- 允许 my.system.app 访问所有隐藏 API -->
<hidden-api-whitelisted-app package name="com.example.myclientapp" />
</whitelist>
优点:
- 无需修改任何 Java 代码或注解。
- 不改变 API 的可见性,其他应用仍然无法访问。
- 只需在编译前配置一次,后续系统会直接对该包名放行所有隐藏 API 检查。
为什么我们推荐第二种方式?
在大多数定制开发场景中,新增系统服务的目的是专门给某一个或几个特定应用提供能力 (例如系统设置、控制面板、工厂测试工具)。我们不希望这个 API 被其他随便安装的普通应用获取到,也不需要将 API 变成公开或系统公开 SDK。
使用白名单方式既满足了调用需求,又保持了 API 的"隐藏"性质,安全且简单。
代码层面是如何实现的?
这个白名单机制的核心判断逻辑在 frameworks/base/core/java/android/content/pm/ApplicationInfo.java 中的 isAllowedToUseHiddenApis() 方法。
以下是源码中相关的方法:
java
private boolean isAllowedToUseHiddenApis() {
if (isSignedWithPlatformKey()) {
return true;
} else if (isSystemApp() || isUpdatedSystemApp()) {
return usesNonSdkApi() || isPackageWhitelistedForHiddenApis();
} else {
return false;
}
}
private boolean isPackageWhitelistedForHiddenApis() {
return (privateFlagsExt & PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS) != 0;
}
可以看到,系统会按顺序检查:
- 应用是否使用平台签名(system 或 platform 证书)。
- 应用是否为系统应用(
isSystemApp)。 - 系统应用是否在 manifest 中声明了
<uses-non-sdk-api>标记。 - 系统应用的包名是否出现在我们提到的
hiddenapi-package-whitelist.xml中。
另一种"野路子":直接修改 isAllowedToUseHiddenApis
既然我们已经找到这个方法的源码,理论上可以直接修改它,比如:
java
if (packageName.equals("com.example.myclientapp")) {
return true;
}
这种方法当然可以解决问题,而且免去了维护白名单 XML 文件的步骤。但是不推荐,因为:
- 每次升级 AOSP 版本或合并补丁时,需要重新 patch 这个文件,容易遗漏。
- 代码逻辑与数据(允许哪些应用)耦合,降低可维护性。
- 白名单 XML 是由系统统一管理的,可以随时添加或删除条目而不需要重写系统服务逻辑。
因此,还是使用官方的白名单 XML 更符合 Android 设计规范。
总结
| 场景 | 解决方案 | 推荐度 |
|---|---|---|
| 新增的系统 API 需要被多个系统应用或系统组件调用 | 使用 @SystemApi + api 文件 |
一般 |
| 新增的系统 API 只被 1~2 个特定应用调用 | 把应用包名加入 hiddenapi-package-whitelist.xml |
✅ 强烈推荐 |
| 临时调试,不想修改源码之外的任何东西 | 直接修改 ApplicationInfo.java 中的判断逻辑 |
❌ 不推荐 |
- Android 官方文档:Hidden API Restrictions
- AOSP 源码路径:
frameworks/base/core/java/android/content/pm/ApplicationInfo.java - AOSP 白名单示例:
frameworks/base/data/etc/hiddenapi-package-whitelist.xml