从 Android 9(API 级别 28)开始,平台限制了您的应用可以使用哪些非 SDK 接口。这些限制适用于应用引用非 SDK 接口或尝试使用反射或 JNI 获取其句柄的任何情况。实施这些限制是为了帮助改善用户和开发者体验,并降低用户崩溃和开发者紧急回滚的风险。有关此决定的更多信息,请参阅 通过减少非 SDK 接口的使用来提高稳定性。
区分 SDK 和非 SDK 接口
一般来说,公共 SDK 接口是在 Android 框架 软件包索引 中记录的那些接口。非 SDK 接口的处理是 API 隐藏的实现细节,因此这些接口可能会在未经通知的情况下发生更改。
为了避免崩溃和意外行为,应用应该只使用 SDK 中正式记录的类部分。这也意味着,当您使用反射等机制与类交互时,不应访问 SDK 中未列出的方法或字段。
非 SDK API 列表
随着每个 Android 版本的发布,都会限制更多非 SDK 接口。我们知道这些限制可能会影响您的发布工作流程,我们希望确保您拥有检测非 SDK 接口用法的工具、向我们提供反馈的机会以及计划和调整新策略的时间。
为了最大程度地减少非 SDK 限制对开发工作流程的影响,非 SDK 接口被划分为多个列表,这些列表根据目标 API 级别定义了其使用受限的程度。下表描述了每个列表
列表 | 代码标签 | 描述 |
---|---|---|
黑名单 |
|
无论应用的 目标 API 级别 如何,都不能使用的非 SDK 接口。如果您的应用尝试访问这些接口之一,系统将 抛出错误。 |
条件性阻止 |
|
从 Android 9(API 级别 28)开始,每个 API 级别都有一些非 SDK 接口,当应用以该 API 级别为目标时,这些接口将受到限制。 这些列表以应用可以定位的最大 API 级别 ( 如果您的应用尝试访问对您的目标 API 级别受限的接口,系统将 将其视为黑名单中的 API。 |
不受支持 |
|
不受限制且应用可以使用 的非 SDK 接口。但是,请注意,这些接口不受支持,并且可能会在未经通知的情况下发生更改。预计这些接口将在将来的 Android 版本中以 max-target-x 列表的形式被条件性阻止。 |
SDK |
|
可以自由使用,现在作为 Android 框架 软件包索引 中正式记录的一部分支持的接口。 |
测试 API |
|
用于内部系统测试的接口,例如通过兼容性测试套件 (CTS) 促进测试的 API。测试 API不是 SDK 的一部分。 从 Android 11(API 级别 30)开始,测试 API 包含在黑名单中,因此应用不允许使用它们,无论其目标 API 级别如何。所有测试 API 均不受支持,并且可能会在未经通知的情况下发生更改,无论平台 API 级别如何。 |
虽然您可以使用一些非 SDK 接口(取决于应用的目标 API 级别),但使用任何非 SDK 方法或字段始终存在很高的应用崩溃风险。如果您的应用依赖于非 SDK 接口,则应开始计划迁移到 SDK 接口或其他替代方案。如果找不到应用功能的非 SDK 接口替代方案,则应 请求新的公共 API。
确定接口属于哪个列表
非 SDK 接口列表是作为平台的一部分构建的。有关每个 Android 版本的信息,请参阅以下部分。
Android 15
对于 Android 15(API 级别 35),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表
SHA-256 校验和:40134e205e58922a708c453726b279a296e6a1f34a988abd90cec0f3432ea5a9
要了解有关 Android 15 中非 SDK API 列表更改的更多信息,请参阅 Android 15 中非 SDK 接口限制的更新。
Android 14
对于 Android 14(API 级别 34),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表。
SHA-256 校验和:7e00db074cbe51c51ff4b411f7b48e98692951395c5c17d069c822cc1d0eae0f
要了解有关 Android 14 中非 SDK API 列表更改的更多信息,请参阅 Android 14 中非 SDK 接口限制的更新。
Android 13
对于 Android 13(API 级别 33),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表。
SHA-256 校验和:233a277aa8ac475b6df61bffd95665d86aac6eb2ad187b90bf42a98f5f2a11a3
要了解有关 Android 13 中非 SDK API 列表更改的更多信息,包括 Android 13 中条件阻止的 API 的建议公共 API 替代方案,请参阅 Android 13 中非 SDK 接口限制的更新。
Android 12
对于 Android 12(API 级别 31),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表。
SHA-256 校验和:40674ff4291eb268f86561bf687e69dbd013df9ec9531a460404532a4ac9a761
要了解有关 Android 12 中非 SDK API 列表更改的更多信息,包括 Android 12 中条件阻止的 API 的建议公共 API 替代方案,请参阅 Android 12 的列表更改。
Android 11
对于 Android 11(API 级别 30),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表。
SHA-256 校验和:a19d839f4f61dc9c94960ae977b2e0f3eb30f880ba1ffe5108e790010b477a56
要了解有关 Android 11 中非 SDK API 列表更改的更多信息,包括 Android 11 中条件阻止的 API 的建议公共 API 替代方案,请参阅 Android 11 的列表更改。
Android 10
对于 Android 10(API 级别 29),您可以下载以下文件,其中描述了所有非 SDK 接口及其对应的列表。
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 时,您可以生成一个包含所有非 SDK 接口及其对应列表的 hiddenapi-flags.csv
文件。为此,请下载 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 二进制文件,但您可以通过使用 Windows 子系统 Linux (WSL) 执行 Linux 二进制文件来在 Windows 上运行 veridex 工具。在按照本节中的步骤操作之前,请安装 WSL 并选择 Ubuntu 作为您的 Linux 发行版。
安装 Ubuntu 后,启动 Ubuntu 终端,然后按照以下步骤操作
- 从 Android 运行时预构建存储库下载veridex 工具。
- 解压缩
appcompat.tar.gz
文件的内容。 - 在解压缩的文件夹中,找到
veridex-linux.zip
文件并将其解压缩。 导航到解压缩的文件夹,然后运行以下命令,其中
your-app.apk
是您要测试的 APK./appcompat.sh --dex-file=your-app.apk
macOS
要在 macOS 上运行 veridex 工具,请按照以下步骤操作
- 从 Android 运行时预构建存储库下载veridex 工具。
- 解压缩
appcompat.tar.gz
文件的内容。 - 在解压缩的文件夹中,找到
veridex-mac.zip
文件并将其解压缩。 导航到解压缩的文件夹,然后运行以下命令,其中
/path-from-root/your-app.apk
是您要测试的 APK 的路径,从系统的根目录开始./appcompat.sh --dex-file=/path-from-root/your-app.apk
Linux
要在 Linux 上运行 veridex 工具,请按照以下步骤操作
- 从 Android 运行时预构建存储库下载veridex 工具。
- 解压缩
appcompat.tar.gz
文件的内容。 - 在解压缩的文件夹中,找到
veridex-linux.zip
文件并将其解压缩。 导航到解压缩的文件夹,然后运行以下命令,其中
your-app.apk
是您要测试的 APK./appcompat.sh --dex-file=your-app.apk
使用 Android Studio lint 工具进行测试
在 Android Studio 中构建应用时,lint 工具 会检查代码中是否存在潜在问题。如果您的应用使用非 SDK 接口,则可能会看到构建错误或警告,具体取决于这些接口所属的列表。
您还可以从命令行运行 lint 工具或手动对特定项目、文件夹或文件运行检查。
使用 Play Console 进行测试
将应用上传到 Play Console 中的测试轨道时,系统会自动测试应用中是否存在潜在问题,并生成预发布报告。如果您的应用使用非 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 可以将自己的接口添加到黑名单中,但不能从 AOSP 非 SDK API 列表中删除接口。CDD 阻止此类更改,CTS 测试确保 Android 运行时正在执行该列表。
关于相关应用兼容性的问题
本机代码中是否存在对非 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
)。