对非 SDK 接口的限制

Android 9(API 级别 28)引入了针对非 SDK 接口的使用限制,无论是直接使用还是通过反射或 JNI 间接使用。 无论应用是引用非 SDK 接口还是尝试使用反射或 JNI 获取其句柄,均适用这些限制。 有关此决定的详细信息,请参阅通过减少使用非 SDK 接口提升稳定性

一般来说,应用应当仅使用 SDK 中正式记录的类。 特别是,这意味着,在您通过反射之类的语义来操作某个类时,不应打算访问 SDK 中未列出的函数或字段。

使用此类函数或字段很可能会破坏您的应用。

区分 SDK 接口和非 SDK 接口

一般来说,SDK 接口是指在 Android 框架软件包索引中记录的接口。 对非 SDK 接口的处理是 API 抽象化的实现细节;其会随时更改,恕不另行通知。

Android 9 引入了针对非 SDK 接口的使用限制,无论是直接使用还是通过反射或 JNI 间接使用。 无论应用是引用非 SDK 接口还是尝试使用反射或 JNI 获取其句柄,均适用这些限制。

测试非 SDK 接口

您可以通过下载 Android 9 对您的应用进行测试。系统将打印日志,如果您的应用访问某些“列入灰名单的”非 SDK 接口,系统还可能显示 toast。 如果您的应用调用“列入黑名单的”非 SDK 接口,系统将引发错误。

注意 toast,它会提醒您注意被建议禁用的接口。 此外,确保检查应用的日志消息,其中包含关于应用所访问的非 SDK 接口的更多详细信息,包括以 Android 运行时所使用的格式列出的声明类、名称和类型。 日志消息还说明了访问方法:直接、通过反射或者通过 JNI。 最后,日志消息显示调用的非 SDK 接口属于灰名单还是黑名单。

您可以使用 adb logcat 访问这些日志消息,消息将显示在正在运行的应用的 PID 下。 例如,日志中的条目看上去可能类似下面这样:

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

列入灰名单的非 SDK 接口包含可以在 Android 9 中继续工作,但我们不能保证在未来版本的平台中能够继续访问的函数和字段。 如果由于某种原因,您不能实现列入灰名单的 API 的替代策略,则可以提交错误,以便请求重新考虑此限制。

某些 API 位于“黑名单”中,比列入灰名单的函数更为严格。 列入黑名单的函数会使应用引发如保留非 SDK 接口的结果部分的表中所列的异常。

Android 9 支持非 SDK 接口的 StrictMode(称为 detectNonSdkApiUsage),因此当您的应用调用列入灰名单的应用时,堆叠追踪将显示上下文。

保留非 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 接口?

它们是不属于官方 Android SDK 的 Java 字段和函数。 它们属于实现详情,如有更改,恕不另行通知。

 

Android 9 中有什么变化?

我们打算设定对使用非 SDK 接口的限制。 这些限制的形式各不相同(请参阅如何在应用运行时检测非 SDK 接口使用?),但是如果您使用非 SDK 接口,它们的形式可能影响您的应用的行为。

 

如果我当前使用非 SDK 接口,我应当在哪请求?

请提交功能请求,并提供您的用例的详细信息。

 

用于限制非 SDK 接口的不同名单有哪些,它们在限制性行为方面的对应含义是什么?

下面是名单类型:

  • 白名单:SDK
  • 浅灰名单:仍可以访问的非 SDK 函数/字段。
  • 深灰名单:
    • 对于目标 SDK 低于 API 级别 28 的应用,允许使用深灰名单接口。
    • 对于目标 SDK 为 API 28 或更高级别的应用:行为与黑名单相同
  • 黑名单:受限,无论目标 SDK 如何。 平台将表现为似乎接口并不存在。 例如,无论应用何时尝试使用接口,平台都会引发 NoSuchMethodError/NoSuchFieldException,即使应用想要了解某个特殊类别的字段/函数名单,平台也不会包含接口。

 

我的应用使用了许多第三方库,这让我很难查找任何正在使用的不公开 API。 有没有编译时工具可以帮助我跟踪违规?

您可以尝试 AOSP 中提供的静态分析工具 veridex

 

如何在应用运行时检测非 SDK 接口使用?

将打印一条 Accessing hidden field|method ... 形式的 logcat 警告。 此外,将为可调试应用显示 toast 消息,并为遭到拒绝的任何非 SDK 接口打印 logcat 消息。

 

Google 如何确保通过 Issue Tracker 捕获所有应用的需求?

我们最初通过对应用进行静态分析确定了种子名单,并辅以下列措施:

  • 对热门 Play 和非 Play 应用进行手动测试
  • 内部报告
  • 自动从内部用户收集数据
  • 开发者预览版报告
  • 其他旨在谨慎地包含更多误报的静态分析

 

如何启用对非 SDK API 的访问?

可以使用 adb 在开发设备上启用对非 SDK API 的访问。

如果您想要在 adb logcat 输出中查看 API 访问信息,则可以更改 API 强制政策:

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

要将其重置为默认设置,请执行以下操作:

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

这些命令不要求已取得 root 权限的设备。

给定整数的含义如下所示:

  • 0:停用非 SDK API 使用检测。 这还会停用日志记录,并且也会破坏严格模式 API detectNonSdkApiUsage()。 不建议使用此值。
  • 1:“只是警告”- 允许访问所有非 SDK API,但会在日志中保留警告。 严格模式 API 将继续工作。
  • 2:不允许使用列入深灰名单和黑名单的 API。
  • 3:不允许使用列入黑名单的 API,但允许使用列入深灰名单的 API。

关于 API 名单

 

Android 9 版本构建中的深灰名单包含什么?

深灰名单包含我们在开发期间未看到使用,但是我们可能已经忽视的函数/字段。 列入深灰名单的函数/字段是那些与 SDK 和浅灰名单中的公开接口紧密相关的函数/字段。

 

灰名单/黑名单位于什么地方?

它们作为平台的一部分构建。 您可以在此处找到预构建条目:

  • platform/prebuilts/runtime/appcompat/hiddenapi-light-greylist.txt:浅灰 API 的 AOSP 名单
  • platform/prebuilts/runtime/appcompat/hiddenapi-dark-greylist.txt:深灰 API 的 AOSP 名单

此外,此名单还包含浅灰 API 的 SDK 28 名单。

黑名单和深灰名单在构建时衍生而来。 我们已添加了一个可以在 AOSP 上生成名单的构建规则。 它与 P 中的黑名单不同,但是重叠比较合理。 开发者可以下载 AOSP,然后使用以下命令生成黑名单:

  make hiddenapi-aosp-blacklist

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

  out/target/common/obj/PACKAGING/hiddenapi-aosp-blacklist.txt

 

这些名单的适当大小是多少?

名单大小的近似值如下所示:

  • 白名单(也称为 SDK)~= 74,000 个函数和字段
  • 浅灰名单 ~= 11,000 个函数和字段
  • 深灰名单 ~= 121,000 个函数和字段
  • 黑名单 ~= 9,000 个函数和字段

 

我可以在系统镜像中的什么地方找到黑名单/灰名单?

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

 

黑名单/灰名单在采用相同 Android 版本的原始设备制造商设备上是否相同?

相同,原始设备制造商可以向黑名单中添加自己的 API,但无法从原始的/AOSP 黑名单或灰名单中移除条目。 CDD 可以防止此类变化,CTS (Compatibility Test Suite) 测试可以确保 Android 运行时强制执行名单。

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

SDK 涵盖 Java 语言。 对于原生 C/C++ 代码的 NDK,我们在 Android Nougat 中完成了此操作。 请参阅: 在 Android N 中使用不公开 C/C++ 符号限制提升稳定性

 

你们有没有任何限制 dex2oat 或 dex 文件操作的计划?

我们没有限制访问 dex2oat 二进制文件的有效计划,但是我们不准备让 dex 文件格式保持稳定,或让公开接口超越在 Dalvik 可执行文件格式中公开指定的部分。 我们保留随时修改或消除 dex2oat 以及 dex 格式未指定部分的权利。 另请注意,dex2oat 生成的衍生文件(例如,odex(也称为 oat)、vdex 和 cdex)全都是未指定格式。

 

如果某个关键的 3p SDK(尤其是代码混淆工具)无法阻止使用非 SDK 接口,但却承诺保持与未来的 Android 版本兼容,该怎么办?Android 会放弃警告吗?

我们没有按 SDK 放弃兼容性要求的计划。 如果 SDK 合作伙伴感到他们无法保持与当前的白名单和灰名单 API 集兼容,他们应当提交错误请求,请求他们需要的非 SDK 函数。

注:对于 Android 9,我们没有计划限制目前正由任何 Android 应用或 SDK 使用的任何非 SDK 接口,但在将来,当我们认为存在适用的 SDK 替代者时,我们会计划开始施加目标 SDK 限制。 请注意,由于使用非 SDK 和非 NDK 接口,并且必须适应,每年其中的许多 SDK 都会遭到破坏。

 

非 SDK 接口限制适用于包括系统和第三方应用在内的所有应用,还是仅适用于第三方应用?

适用于所有应用,但是我们会豁免使用平台密钥签署的应用,并且我们也有一个针对系统镜像应用的软件包白名单。 请注意,这些豁免仅适用于系统镜像中的应用(或更新的系统镜像应用)。 名单仅用于针对不公开平台 API 而不是 SDK API(即, LOCAL_PRIVATE_PLATFORM_APIS := true)构建的应用。

 

一些开发者已经发布不公开 API 限制机制和绕过技术。 你们对此作何评论?你们会让限制更加严格吗?

我们已注意到潜在的解决方法。 令人欣喜的是,大多数应用仍在坚持使用公开 API。 在尝试让调用非 SDK 接口变得更困难以确保兼容性的同时,我们也会在这种做法与保持运行时易于调试之间作出平衡。 我们将继续评估底层实现并与开发者合作。