对非 SDK 接口的限制

从 Android 9(API 级别 28)开始,平台限制了应用可以使用的非 SDK 接口。每当应用引用非 SDK 接口或尝试使用反射或 JNI 获取其句柄时,这些限制均适用。实施这些限制是为了帮助改善用户和开发者体验,并降低用户崩溃的风险以及开发者紧急发布补丁的风险。有关此决定的更多信息,请参阅通过减少非 SDK 接口的使用来提高稳定性

区分 SDK 接口和非 SDK 接口

通常来说,公开 SDK 接口是指 Android 框架软件包索引中记录的接口。非 SDK 接口的处理是 API 抽象出来的一个实现细节,因此这些接口可能会在不另行通知的情况下发生更改。

为避免崩溃和意外行为,应用应仅使用 SDK 中类的官方文档部分。这意味着在使用反射等机制与类交互时,您不应访问 SDK 中未列出的方法或字段。

非 SDK API 列表

随着 Android 的每个发布版本,都会有更多非 SDK 接口受到限制。我们知道这些限制可能会影响您的发布工作流程,我们希望确保您拥有检测非 SDK 接口使用情况的工具,有机会向我们提供反馈,并有时间规划和调整以适应新政策。

为了最大限度地减少非 SDK 限制对您的开发工作流程的影响,非 SDK 接口被划分为多个列表,这些列表根据所针对的 API 级别定义了其使用受限的严格程度。下表介绍了这些列表:

列表 代码标签 说明
阻止列表
  • blocked
  • 已弃用:blacklist
无论应用的目标 API 级别如何,您都无法使用的非 SDK 接口。如果您的应用尝试访问其中一个接口,系统会抛出错误
有条件地阻止
  • max-target-x
  • 已弃用:greylist-max-x

从 Android 9(API 级别 28)开始,每个 API 级别都有非 SDK 接口,当应用以该 API 级别为目标时,这些接口会受到限制。

这些列表根据应用可以作为目标的最大 API 级别 (max-target-x) 进行标记,超过该级别后,应用将无法再访问该列表中的非 SDK 接口。例如,在 Android Pie 中未阻止但在 Android 10 中现已阻止的非 SDK 接口属于 max-target-p (greylist-max-p) 列表,其中“p”代表 Pie 或 Android 9(API 级别 28)。

如果您的应用尝试访问针对您的目标 API 级别受限的接口,系统会像处理阻止列表中的 API 一样处理该 API

不支持
  • unsupported
  • 已弃用:greylist
不受限制且您的应用可以使用的非 SDK 接口。但请注意,这些接口不受支持,并且可能会在不另行通知的情况下发生更改。预计这些接口将在未来的 Android 版本中被有条件地阻止,并列入 max-target-x 列表。
SDK
  • 同时包含 public-apisdk
  • 已弃用:同时包含 public-apiwhitelist
可以自由使用并现已作为官方文档 Android 框架软件包索引一部分受支持的接口。
测试 API
  • test-api
用于内部系统测试的接口,例如通过兼容性测试套件 (CTS) 促进测试的 API。测试 API 属于 SDK。 从 Android 11(API 级别 30)开始,测试 API 被包含在阻止列表中,因此无论目标 API 级别如何,应用都无法使用它们。所有测试 API 均不受支持,并且可能会在不另行通知的情况下发生更改,无论平台 API 级别如何。

虽然您可以使用某些非 SDK 接口(取决于您的应用的目标 API 级别),但使用任何非 SDK 方法或字段始终存在导致应用崩溃的高风险。如果您的应用依赖非 SDK 接口,您应该开始计划迁移到 SDK 接口或其他替代方案。如果您的应用中的某个功能找不到使用非 SDK 接口的替代方案,您应该请求新的公开 API

确定接口属于哪个列表

非 SDK 接口列表作为平台的一部分构建。有关每个 Android 发布版本的信息,请参阅以下部分。

Android 16

对于 Android 16(API 级别 36),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表:

文件:hiddenapi-flags.csv

SHA-256 校验和:9102af02fe6ab68b92464bdff5e5b09f3bd62c65d1130aaf85d3296f17d38074

要详细了解 Android 16 中非 SDK API 列表的变化,请参阅Android 16 中非 SDK 接口限制的更新

Android 15

对于 Android 15(API 级别 35),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表:

文件:hiddenapi-flags.csv

SHA-256 校验和:40134e205e58922a708c453726b279a296e6a1f34a988abd90cec0f3432ea5a9

要详细了解 Android 15 中非 SDK API 列表的变化,请参阅Android 15 中非 SDK 接口限制的更新

Android 14

对于 Android 14(API 级别 34),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表:

文件:hiddenapi-flags.csv

SHA-256 校验和:7e00db074cbe51c51ff4b411f7b48e98692951395c5c17d069c822cc1d0eae0f

要详细了解 Android 14 中非 SDK API 列表的变化,请参阅Android 14 中非 SDK 接口限制的更新

Android 13

对于 Android 13(API 级别 33),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表:

文件:hiddenapi-flags.csv

SHA-256 校验和:233a277aa8ac475b6df61bffd95665d86aac6eb2ad187b90bf42a98f5f2a11a3

要详细了解 Android 13 中非 SDK API 列表的变化,包括针对 Android 13 中有条件阻止的 API 建议的公开 API 替代方案,请参阅Android 13 中非 SDK 接口限制的更新

Android 12

对于 Android 12(API 级别 31),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表:

文件:hiddenapi-flags.csv

SHA-256 校验和:40674ff4291eb268f86561bf687e69dbd013df9ec9531a460404532a4ac9a761

要详细了解 Android 12 中非 SDK API 列表的变化,包括针对 Android 12 中有条件阻止的 API 建议的公开 API 替代方案,请参阅Android 12 的列表变化

Android 11

对于 Android 11(API 级别 30),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表:

文件:hiddenapi-flags.csv

SHA-256 校验和:a19d839f4f61dc9c94960ae977b2e0f3eb30f880ba1ffe5108e790010b477a56

要详细了解 Android 11 中非 SDK API 列表的变化,包括针对 Android 11 中有条件阻止的 API 建议的公开 API 替代方案,请参阅Android 11 的列表变化

Android 10

对于 Android 10(API 级别 29),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表:

文件:hiddenapi-flags.csv

SHA-256 校验和:f22a59c215e752777a114bd9b07b0b6b4aedfc8e49e6efca0f99681771c5bfeb

要详细了解 Android 10 中非 SDK API 列表的变化,包括针对 Android 10 中有条件阻止的 API 建议的公开 API 替代方案,请参阅Android 10 的列表变化

Android 9

对于 Android 9(API 级别 28),以下文本文件包含不受限制(灰名单)的非 SDK API 列表:hiddenapi-light-greylist.txt

阻止列表 (blacklist) 和有条件阻止的 API 列表(深灰名单)是在构建时派生的。

从 AOSP 生成列表

使用 AOSP 时,您可以生成一个 hiddenapi-flags.csv 文件,其中包含所有非 SDK 接口及其对应的列表。为此,请下载 AOSP 源代码,然后运行以下命令:

m out/soong/hiddenapi/hiddenapi-flags.csv

然后,您可以在以下位置找到该文件:

out/soong/hiddenapi/hiddenapi-flags.csv

访问受限非 SDK 接口时的预期行为

下表描述了如果您的应用尝试访问属于阻止列表的非 SDK 接口时可能出现的行为。

访问方式 结果
引用字段的 Dalvik 指令 抛出 NoSuchFieldError
引用方法的 Dalvik 指令 抛出 NoSuchMethodError
使用 Class.getDeclaredField()Class.getField() 进行反射 抛出 NoSuchFieldException
使用 Class.getDeclaredMethod(), Class.getMethod() 进行反射 抛出 NoSuchMethodException
使用 Class.getDeclaredFields(), Class.getFields() 进行反射 结果中不包含非 SDK 成员
使用 Class.getDeclaredMethods(), Class.getMethods() 进行反射 结果中不包含非 SDK 成员
使用 env->GetFieldID() 进行 JNI 返回 NULL,抛出 NoSuchFieldError
使用 env->GetMethodID() 进行 JNI 返回 NULL,抛出 NoSuchMethodError

测试您的应用是否存在非 SDK 接口

您可以使用几种方法来测试应用中的非 SDK 接口。

使用可调试应用进行测试

您可以通过在运行 Android 9(API 级别 28)或更高版本的设备或模拟器上构建并运行可调试应用来测试非 SDK 接口。请确保您使用的设备或模拟器与您的应用的目标 API 级别匹配。

在对应用运行测试时,如果您的应用访问了某些非 SDK 接口,系统会打印日志消息。您可以检查应用的日志消息以查找以下详细信息:

  • 声明类、名称和类型(采用 Android 运行时使用的格式)。
  • 访问方式:链接、使用反射或使用 JNI。
  • 非 SDK 接口所属的列表。

您可以使用 adb logcat 访问这些日志消息,这些消息会显示在正在运行的应用的 PID 下。例如,日志中的条目可能如下所示:

Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)

使用 StrictMode API 进行测试

您还可以使用 StrictMode API 来测试非 SDK 接口。使用 detectNonSdkApiUsage 方法来启用此功能。启用 StrictMode API 后,您可以使用 penaltyListener 为每次非 SDK 接口的使用接收回调,您可以在其中实现自定义处理。Violation 回调中提供的对象派生自 Throwable,并且随附的堆栈跟踪提供了使用情况的上下文。

使用 veridex 工具进行测试

您还可以在 APK 上运行 veridex 静态分析工具。veridex 工具会扫描 APK 的整个代码库(包括任何第三方库),并报告其发现的任何非 SDK 接口使用情况。

veridex 工具的局限性包括:

  • 它无法检测通过 JNI 的调用。
  • 它只能检测通过反射进行的部分调用。
  • 它对非活动代码路径的分析仅限于 API 级别检查。
  • 它只能在支持 SSE4.2 和 POPCNT 指令的机器上运行。

Windows

不提供原生 Windows 二进制文件,但您可以通过使用适用于 Linux 的 Windows 子系统 (WSL) 执行 Linux 二进制文件来在 Windows 上运行 veridex 工具。在执行本节中的步骤之前,请安装 WSL 并选择 Ubuntu 作为您的 Linux 发行版。

安装 Ubuntu 后,启动 Ubuntu 终端,然后按照以下步骤操作:

  1. 从 Android 运行时预构建存储库下载 veridex 工具
  2. 提取 appcompat.tar.gz 文件的内容。
  3. 在解压的文件夹中,找到 veridex-linux.zip 文件并解压。
  4. 导航到解压后的文件夹,然后运行以下命令,其中 your-app.apk 是您要测试的 APK:

    ./appcompat.sh --dex-file=your-app.apk
    

macOS

要在 macOS 上运行 veridex 工具,请按照以下步骤操作:

  1. 从 Android 运行时预构建存储库下载 veridex 工具
  2. 提取 appcompat.tar.gz 文件的内容。
  3. 在解压的文件夹中,找到 veridex-mac.zip 文件并解压。
  4. 导航到解压后的文件夹,然后运行以下命令,其中 /path-from-root/your-app.apk 是您要测试的 APK 的路径,从您系统的根目录开始:

    ./appcompat.sh --dex-file=/path-from-root/your-app.apk
    

Linux

要在 Linux 上运行 veridex 工具,请按照以下步骤操作:

  1. 从 Android 运行时预构建存储库下载 veridex 工具
  2. 提取 appcompat.tar.gz 文件的内容。
  3. 在解压的文件夹中,找到 veridex-linux.zip 文件并解压。
  4. 导航到解压后的文件夹,然后运行以下命令,其中 your-app.apk 是您要测试的 APK:

    ./appcompat.sh --dex-file=your-app.apk
    

使用 Android Studio lint 工具进行测试

每当您在 Android Studio 中构建应用时,lint 工具都会检查您的代码是否存在潜在问题。如果您的应用使用非 SDK 接口,您可能会看到构建错误或警告,具体取决于这些接口所属的列表

您还可以从命令行运行 lint 工具,或者在特定项目、文件夹或文件上手动运行检查

使用 Play 管理中心进行测试

当您将应用上传到 Play 管理中心中的测试轨道时,您的应用会自动进行潜在问题测试,并生成预发布报告。如果您的应用使用非 SDK 接口,则预发布报告中会显示错误或警告,具体取决于这些接口所属的列表

有关详细信息,请参阅使用预发布报告识别问题中的 Android 兼容性部分。

请求新的公开 API

如果您的应用中的某个功能找不到使用非 SDK 接口的替代方案,您可以通过在我们的问题跟踪器中创建功能请求来请求新的公开 API。

创建功能请求时,请提供以下信息:

  • 您正在使用的不受支持的 API,包括在 Accessing hidden ... logcat 消息中看到的完整描述符。
  • 您需要使用这些 API 的原因,包括 API 所必需的高级功能详细信息,而不仅仅是低级详细信息。
  • 为什么任何相关的公开 SDK API 不足以满足您的目的。
  • 您尝试过的任何其他替代方案以及这些方案为何不起作用。

当您在功能请求中提供这些详细信息时,会增加获得新的公开 API 的可能性。

其他问题

本部分包含开发者经常提出的一些其他问题的答案:

一般问题

Google 如何确保能够通过问题跟踪器捕捉到所有应用的需求?

我们通过对应用进行静态分析创建了 Android 9(API 级别 28)的初始列表,并辅以以下方法:

  • 对顶级 Play 和非 Play 应用进行手动测试
  • 内部报告
  • 内部用户的自动数据收集
  • 开发者预览报告
  • 旨在保守地包含更多误报的额外静态分析

在评估每个新发布版本的列表时,我们会考虑 API 使用情况以及通过问题跟踪器获得的开发者反馈。

如何启用对非 SDK 接口的访问?

您可以使用 adb 命令更改 API 强制执行政策,从而在开发设备上启用对非 SDK 接口的访问。您使用的命令因 API 级别而异。这些命令不需要已获得 root 权限的设备。

Android 10(API 级别 29)或更高版本

要启用访问,请使用以下 adb

命令

adb shell settings put global hidden_api_policy  1

要将 API 强制执行政策重置为默认设置,请使用以下命令:

adb shell settings delete global hidden_api_policy
Android 9(API 级别 28)

要启用访问,请使用以下 adb 命令:

adb shell settings put global hidden_api_policy_pre_p_apps  1
adb shell settings put global hidden_api_policy_p_apps 1

要将 API 强制执行政策重置为默认设置,请使用以下命令:

adb shell settings delete global hidden_api_policy_pre_p_apps
adb shell settings delete global hidden_api_policy_p_apps

您可以将 API 强制执行政策中的整数设置为以下值之一:

  • 0:禁用所有非 SDK 接口检测。使用此设置会禁用所有非 SDK 接口使用情况的日志消息,并阻止您使用 StrictMode API 测试您的应用。不建议使用此设置。
  • 1:启用对所有非 SDK 接口的访问,但打印包含任何非 SDK 接口使用情况警告的日志消息。使用此设置还允许您使用 StrictMode API 测试您的应用。
  • 2:不允许使用属于阻止列表或针对您的目标 API 级别有条件阻止的非 SDK 接口。

关于非 SDK 接口列表的问题

在哪里可以找到系统映像中的非 SDK API 列表?

它们编码在平台 dex 文件中的字段和方法访问标志位中。系统映像中没有单独的文件包含这些列表。

在不同 OEM 设备上,相同 Android 版本的非 SDK API 列表是否相同?

OEM 可以将自己的接口添加到阻止列表 (blacklist) 中,但不能从 AOSP 非 SDK API 列表中移除接口。CDD 阻止此类更改,CTS 测试确保 Android Runtime 强制执行该列表。

原生代码中的非 NDK 接口是否有任何限制?

Android SDK 包含 Java 接口。平台从 Android 7(API 级别 26)开始限制原生 C/C++ 代码对非 NDK 接口的访问。有关详细信息,请参阅通过 Android N 中的私有 C/C++ 符号限制提高稳定性

是否有计划限制 dex2oat 或 DEX 文件操作?

我们目前没有限制访问 dex2oat 二进制文件的计划,但我们不打算让 DEX 文件格式保持稳定或成为公开接口,除非是Dalvik 可执行文件格式中公开指定的部分。我们保留随时修改或移除 dex2oat 和 DEX 格式中未指定部分的权利。另请注意,由 dex2oat 生成的派生文件,例如 ODEX(也称为 OAT)、VDEX 和 CDEX 都是未指定的格式。

如果一个关键的第三方 SDK(例如混淆器)无法避免使用非 SDK 接口,但承诺保持与未来 Android 版本的兼容性,该怎么办?在这种情况下,Android 是否可以免除其兼容性要求?

我们没有按 SDK 免除兼容性要求的计划。如果 SDK 开发者只能通过依赖不支持的(以前的灰名单)列表中的接口来保持兼容性,他们应该开始计划迁移到 SDK 接口或其他替代方案,并且在找不到使用非 SDK 接口的替代方案时,请求新的公开 API

非 SDK 接口限制是否适用于所有应用,包括系统和第一方应用,而不仅仅是第三方应用?

是的,但是,我们豁免了使用平台密钥签名的应用和一些系统映像应用。请注意,这些豁免仅适用于属于系统映像(或更新的系统映像应用)的应用。此列表仅适用于针对私有平台 API 而非 SDK API 构建的应用(其中 LOCAL_PRIVATE_PLATFORM_APIS := true)。