针对非 SDK 接口的限制

从 Android 9(API 级别 28)开始,Android 平台对应用能使用的非 SDK 接口实施了限制。只要应用引用非 SDK 接口或尝试使用反射或 JNI 来获取其句柄,这些限制就适用。这些限制旨在帮助提升用户体验和开发者体验,为用户降低应用发生崩溃的风险,同时为开发者降低紧急发布的风险。如需详细了解有关此限制的决定,请参阅通过减少非 SDK 接口的使用来提高稳定性

区分 SDK 接口和非 SDK 接口

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

为了避免发生崩溃和意外行为,应用应仅使用 SDK 中经过正式记录的类。这也意味着当您的应用使用反射等机制与类互动时,不应访问 SDK 中未列出的方法或字段。

非 SDK API 名单

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

为了尽可能降低非 SDK 使用限制对开发工作流的影响,我们将非 SDK 接口分成了几个名单,这些名单界定了非 SDK 接口使用限制的严格程度(取决于应用的目标 API 级别)。下表介绍了这些名单:

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

从 Android 9(API 级别 28)开始,当有应用以该 API 级别为目标平台时,我们会在每个 API 级别分别限制某些非 SDK 接口。

这些名单会以应用无法再访问相应名单中的非 SDK 接口之前可以作为目标平台的最高 API 级别 (max-target-x) 进行标记。例如,在 Android Pie 中未被屏蔽,但现在已被 Android 10 屏蔽的非 SDK 接口会列入 max-target-p (greylist-max-p) 名单,其中“p”表示 Pie 或 Android 9(API 级别 28)。

如果您的应用尝试访问受目标 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 均不受支持,且无论平台 API 级别如何,都可能会在不另行通知的情况下随时发生更改。

尽管您仍可以使用某些非 SDK 接口(取决于应用的目标 API 级别),但只要您使用任何非 SDK 方法或字段,终归存在导致应用出问题的显著风险。如果您的应用依赖于非 SDK 接口,建议您开始计划迁移到 SDK 接口或其他替代方案。如果您无法为应用中的功能找到替代方案来不再使用非 SDK 接口,我们建议您请求添加新的公共 API

确定接口属于哪个名单

非 SDK 接口的名单会构建为 Android 平台的一部分。如需了解每个 Android 版本的相关信息,请参阅以下部分。

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 方法来启用此 API。启用 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)或更高版本

如需允许访问非 SDK 接口,请使用以下 adb

命令:

adb shell settings put global hidden_api_policy  1

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

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

如需允许访问非 SDK 接口,请使用以下 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:禁止使用列于屏蔽名单中的非 SDK 接口,或针对您的目标 API 级别被有条件屏蔽的非 SDK 接口。

有关非 SDK 接口名单的问题

在系统映像中的什么位置可以找到非 SDK API 名单?

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

搭载同一 Android 版本的不同 OEM 设备上的非 SDK API 名单是否相同?

原始设备制造商 (OEM) 可以将自己的接口添加到屏蔽名单(黑名单)中,但是无法从 AOSP 非 SDK API 名单中移除接口。CDD 会阻止此类更改,并且 CTS 测试会确保 Android 运行时强制执行相应名单。

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

Android SDK 包含 Java 接口。Android 平台从 Android 7(API 级别 26)开始限制访问原生 C/C++ 代码中的非 NDK 接口。如需了解详情,请参阅通过在 Android N 中限制私有 C/C++ 符号提升稳定性

Google 是否计划限制对 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)。