Android14 新增系统服务后,应用调用出现 “hidden api” 警告的原因与解决方案

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.txtapi/ 目录中,让编译系统识别这是一个"系统公开 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;
    }

可以看到,系统会按顺序检查:

  1. 应用是否使用平台签名(system 或 platform 证书)。
  2. 应用是否为系统应用(isSystemApp)。
  3. 系统应用是否在 manifest 中声明了 <uses-non-sdk-api> 标记。
  4. 系统应用的包名是否出现在我们提到的 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 中的判断逻辑 ❌ 不推荐
相关推荐
赏金术士5 小时前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose
随遇丿而安5 小时前
第5周:XML 资源、样式和主题,真正解决的是“页面以后还改不改得动”
android
zh_xuan6 小时前
Android 获取系统内存页大小:sysconf(_SC_PAGESIZE) 与 JNI 实现
android·jni·ndk·内存页大小
fundroid7 小时前
Google I/O 2026 | Android 全面进化:从操作系统到“智能中枢”
android·jetpack compose·google i/o 2026
zh_xuan8 小时前
Android 复用 .so 库:通过 jniLibs 集成预编译二进制库(获取 Page Size )
android·jni·ndk·内存页大小
匆忙拥挤repeat9 小时前
Android Compose 约束布局
android
好安静9 小时前
Android ShellTransitions 机制完整分析(by DeepSeekV4Pro)
android
事后不诸葛9 小时前
安卓init.rc解析
android·framework
徒手猫9 小时前
myslq 中json 格式的数据如何获取某个属性
android·json