行为变更:所有应用

Android 10 包含可能会影响您应用的行为变更。本页列出的变更适用于在 Android 10 上运行的应用,无论应用的 targetSdkVersion 如何。您应该测试您的应用并根据需要进行修改,以正确支持这些变更。

如果您的应用 targetSdkVersion29 或更高,您还需要支持其他变更。请务必阅读针对以 29 为目标平台的应用的行为变更,了解详细信息。

注意:除了本页列出的变更之外,Android 10 还引入了大量基于隐私的变更和限制,包括以下内容:

  • 后台访问设备位置信息
  • 后台活动启动
  • 联系人关联信息
  • MAC 地址随机化
  • 相机元数据
  • 权限模型

这些变更会影响所有应用并增强用户隐私。如需详细了解如何支持这些变更,请参阅隐私权变更页面。

非 SDK 接口限制

为帮助确保应用稳定性和兼容性,平台在 Android 9(API 级别 28)中开始限制您的应用可使用的非 SDK 接口。Android 10 包含根据与 Android 开发者协作和最新内部测试更新的受限非 SDK 接口列表。我们的目标是确保在限制非 SDK 接口之前提供公共替代方案。

如果您不以 Android 10(API 级别 29)为目标平台,其中一些变更可能不会立即影响您。但是,虽然您目前可以使用一些非 SDK 接口(取决于您应用的 Target API 级别),但使用任何非 SDK 方法或字段始终会带来应用崩溃的高风险。

如果您不确定您的应用是否使用了非 SDK 接口,可以测试您的应用以找出答案。如果您的应用依赖非 SDK 接口,您应该开始计划迁移到 SDK 替代方案。尽管如此,我们理解某些应用在使用非 SDK 接口方面存在有效的用例。如果您无法为应用中的某项功能找到使用非 SDK 接口的替代方案,您应该请求新的公共 API

如需了解详情,请参阅Android 10 中非 SDK 接口限制的更新以及非 SDK 接口限制

手势导航

从 Android 10 开始,用户可以在设备上启用手势导航。如果用户启用手势导航,这将影响设备上的所有应用,无论应用是否以 API 级别 29 为目标平台。例如,如果用户从屏幕边缘滑动,系统会将该手势解释为“返回”导航,除非应用专门针对屏幕的某些部分覆盖该手势

为了使您的应用与手势导航兼容,您需要将应用内容从边缘扩展到边缘,并适当处理冲突的手势。如需了解信息,请参阅手势导航文档。

NDK

Android 10 包含以下 NDK 变更。

共享对象不能包含文本重定位

Android 6.0(API 级别 23)禁止在共享对象中使用文本重定位。代码必须按原样加载,不得修改。此变更可缩短应用加载时间并提高安全性。

SELinux 对以 Android 10 或更高版本为目标平台的应用强制执行此限制。如果这些应用继续使用包含文本重定位的共享对象,它们将面临很高的崩溃风险。

Bionic 库和动态链接器路径的变更

从 Android 10 开始,一些路径是符号链接而非常规文件。依赖路径为常规文件的应用可能会崩溃

  • /system/lib/libc.so -> /apex/com.android.runtime/lib/bionic/libc.so
  • /system/lib/libm.so -> /apex/com.android.runtime/lib/bionic/libm.so
  • /system/lib/libdl.so -> /apex/com.android.runtime/lib/bionic/libdl.so
  • /system/bin/linker -> /apex/com.android.runtime/bin/linker

这些变更也适用于文件的 64 位变体,其中 lib/ 替换为 lib64/

为了兼容性,在旧路径提供了符号链接。例如,/system/lib/libc.so 是指向 /apex/com.android.runtime/lib/bionic/libc.so 的符号链接。因此 dlopen(“/system/lib/libc.so”) 仍可正常运行,但应用在实际尝试通过读取 /proc/self/maps 或类似文件来检查加载的库时会发现差异,这不常见,但我们发现有些应用会将其作为反黑客过程的一部分。如果是这样,应将 /apex/… 路径添加为 Bionic 文件的有效路径。

系统二进制文件/库映射到仅执行内存

从 Android 10 开始,系统二进制文件和库的可执行段被映射到只执行(不可读)内存中,作为对抗代码重用攻击的强化技术。如果您的应用对标记为只执行的内存段执行读取操作——无论是由于 bug、漏洞还是有意进行内存检查——系统都会向您的应用发送 SIGSEGV 信号。

您可以通过检查 /data/tombstones/ 中相关的 tombstone 文件来确定此行为是否导致了崩溃。与只执行相关的崩溃包含以下中止消息:

Cause: execute-only (no-read) memory access error; likely due to data in .text.

为了解决此问题以执行内存检查等操作,可以通过调用 mprotect() 将只执行段标记为读+执行。但是,我们强烈建议在此之后将其设置回只执行,因为此访问权限设置可为您的应用和用户提供更好的保护。

安全性

Android 10 包含以下安全变更。

TLS 1.3 默认启用

在 Android 10 及更高版本中,TLS 1.3 默认对所有 TLS 连接启用。以下是我们 TLS 1.3 实现的一些重要细节:

  • TLS 1.3 密码套件无法自定义。启用 TLS 1.3 时,支持的 TLS 1.3 密码套件始终启用。任何通过调用 setEnabledCipherSuites() 禁用它们的尝试都将被忽略。
  • 当协商 TLS 1.3 时,HandshakeCompletedListener 对象在会话添加到会话缓存之前被调用。(在 TLS 1.2 和其他早期版本中,这些对象在会话添加到会话缓存之后被调用。)
  • 在某些情况下,SSLEngine 实例在早期 Android 版本上会抛出 SSLHandshakeException,而在 Android 10 及更高版本上,这些实例将改为抛出 SSLProtocolException
  • 不支持 0-RTT 模式。

如果需要,您可以通过调用 SSLContext.getInstance("TLSv1.2") 来获取已禁用 TLS 1.3 的 SSLContext。您还可以通过在适当的对象上调用 setEnabledProtocols(),以每个连接为基础启用或禁用协议版本。

TLS 中不信任使用 SHA-1 签名的证书

在 Android 10 中,使用 SHA-1 哈希算法的证书在 TLS 连接中不被信任。自 2016 年以来,根 CA 未再颁发此类证书,并且它们在 Chrome 或其他主流浏览器中不再受信任。

如果连接到使用 SHA-1 呈现证书的网站,任何连接尝试都将失败。

KeyChain 行为变更和改进

一些浏览器(例如 Google Chrome)允许用户在 TLS 服务器作为 TLS 握手的一部分发送证书请求消息时选择证书。从 Android 10 开始,KeyChain 对象在调用 KeyChain.choosePrivateKeyAlias() 以向用户显示证书选择提示时,会遵守颁发者和密钥规范参数。特别是,此提示不包含不符合服务器规范的选项。

如果没有可供用户选择的证书(例如没有证书与服务器规范匹配或设备未安装任何证书),则根本不会出现证书选择提示。

此外,在 Android 10 或更高版本上,将密钥或 CA 证书导入 KeyChain 对象时不需要设备屏幕锁定。

其他 TLS 和加密变更

TLS 和加密库中已发生几项次要变更,它们在 Android 10 上生效:

  • AES/GCM/NoPadding 和 ChaCha20/Poly1305/NoPadding 密码从 getOutputSize() 返回更精确的缓冲区大小。
  • 当最大协议为 TLS 1.2 或更高时,连接尝试中会省略 TLS_FALLBACK_SCSV 密码套件。由于 TLS 服务器实现的改进,我们不建议尝试 TLS 外部回退。相反,我们建议依赖 TLS 版本协商。
  • ChaCha20-Poly1305 是 ChaCha20/Poly1305/NoPadding 的别名。
  • 带尾随点的 hostname 不被视为有效的 SNI hostname。
  • 在选择用于证书响应的签名密钥时,会遵循 CertificateRequest 中的 supported_signature_algorithms 扩展。
  • 不透明签名密钥(例如来自 Android Keystore 的密钥)可以与 TLS 中的 RSA-PSS 签名一起使用。

Wi-Fi Direct 广播

在 Android 10 上,以下与Wi-Fi Direct相关的广播不是黏性广播:

如果您的应用曾依赖在注册时接收这些广播(因为它们是黏性广播),则应在初始化时使用适当的 get() 方法来获取信息。

Wi-Fi Aware 功能

Android 10 添加了支持,可简化使用 Wi-Fi Aware 数据路径创建 TCP/UDP 套接字。要创建连接到 ServerSocket 的 TCP/UDP 套接字,客户端设备需要知道服务器的 IPv6 地址和端口。这以前需要通过带外通信(例如使用蓝牙或 Wi-Fi Aware 第 2 层消息传递),或使用其他协议(例如 mDNS)带内发现。借助 Android 10,信息可以作为网络设置的一部分进行通信。

服务器可以执行以下任一操作:

  • 初始化 ServerSocket 并设置或获取要使用的端口。
  • 将端口信息指定为 Wi-Fi Aware 网络请求的一部分。

以下代码示例展示了如何将端口信息指定为网络请求的一部分:

Kotlin

val ss = ServerSocket()
val ns = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
  .setPskPassphrase("some-password")
  .setPort(ss.localPort)
  .build()

val myNetworkRequest = NetworkRequest.Builder()
  .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
  .setNetworkSpecifier(ns)
  .build()

Java

ServerSocket ss = new ServerSocket();
WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier
  .Builder(discoverySession, peerHandle)
  .setPskPassphrase(some-password)
  .setPort(ss.getLocalPort())
  .build();

NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
  .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
  .setNetworkSpecifier(ns)
  .build();

然后,客户端执行 Wi-Fi Aware 网络请求以获取服务器提供的 IPv6 和端口。

Kotlin

val callback = object : ConnectivityManager.NetworkCallback() {
  override fun onAvailable(network: Network) {
    ...
  }
  
  override fun onLinkPropertiesChanged(network: Network,
      linkProperties: LinkProperties) {
    ...
  }

  override fun onCapabilitiesChanged(network: Network,
      networkCapabilities: NetworkCapabilities) {
    ...
    val ti = networkCapabilities.transportInfo
    if (ti is WifiAwareNetworkInfo) {
       val peerAddress = ti.peerIpv6Addr
       val peerPort = ti.port
    }
  }
  override fun onLost(network: Network) {
    ...
  }
};

connMgr.requestNetwork(networkRequest, callback)

Java

callback = new ConnectivityManager.NetworkCallback() {
  @Override
  public void onAvailable(Network network) {
    ...
  }
  @Override
  public void onLinkPropertiesChanged(Network network,
      LinkProperties linkProperties) {
    ...
  }
  @Override
  public void onCapabilitiesChanged(Network network,
      NetworkCapabilities networkCapabilities) {
    ...
    TransportInfo ti = networkCapabilities.getTransportInfo();
    if (ti instanceof WifiAwareNetworkInfo) {
       WifiAwareNetworkInfo info = (WifiAwareNetworkInfo) ti;
       Inet6Address peerAddress = info.getPeerIpv6Addr();
       int peerPort = info.getPort();
    }
  }
  @Override
  public void onLost(Network network) {
    ...
  }
};

connMgr.requestNetwork(networkRequest, callback);

Go 设备上的 SYSTEM_ALERT_WINDOW

在 Android 10 (Go edition) 设备上运行的应用无法获得 SYSTEM_ALERT_WINDOW 权限。这是因为绘制叠加窗口会占用过多内存,这对低内存 Android 设备的性能尤其有害。

如果运行 Android 9 或更低版本的 Go edition 设备上的应用获得了 SYSTEM_ALERT_WINDOW 权限,即使设备升级到 Android 10,该应用也会保留该权限。但是,设备升级后,尚未拥有该权限的应用将无法获得该权限。

如果 Go 设备上的应用发送了带有操作 ACTION_MANAGE_OVERLAY_PERMISSION 的 Intent,系统会自动拒绝该请求,并将用户带到“设置”屏幕,其中显示不允许该权限,因为它会减慢设备速度。如果 Go 设备上的应用调用 Settings.canDrawOverlays(),该方法始终返回 false。同样,这些限制不适用于在设备升级到 Android 10 之前已获得 SYSTEM_ALERT_WINDOW 权限的应用。

针对旧版 Android 应用的警告

运行 Android 10 或更高版本的设备在用户首次运行任何目标平台为 Android 5.1(API 级别 22)或更低版本的应用时,会向用户发出警告。如果应用需要用户授予权限,用户还可以有机会在应用首次运行之前调整应用的权限。

由于 Google Play 的目标 API 要求,用户仅在运行未最近更新的应用时才会看到这些警告。对于通过其他商店分发的应用,类似的 target API 要求正在 2019 年生效。有关这些要求的更多信息,请参阅2019 年扩展目标 API 级别要求

移除 SHA-2 CBC 密码套件

以下 SHA-2 CBC 密码套件已从平台中移除:

  • TLS_RSA_WITH_AES_128_CBC_SHA256
  • TLS_RSA_WITH_AES_256_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384

这些密码套件不如使用 GCM 的类似密码套件安全,并且大多数服务器要么同时支持这些密码套件的 GCM 和 CBC 变体,要么都不支持。

应用使用情况

Android 10 引入了以下与应用使用情况相关的行为变更:

  • UsageStats 应用使用情况改进 - 在分屏模式或画中画模式下使用应用时,Android 10 会使用 UsageStats 准确跟踪应用使用情况。此外,Android 10 会正确跟踪即时应用使用情况。

  • 按应用灰度显示 - Android 10 可以按应用设置灰度显示模式。

  • 按应用干扰状态 - Android 10 可以选择性地将应用设置为“干扰状态”,在此状态下,其通知将被抑制,并且它们不会显示为建议的应用。

  • 暂停和播放 - 在 Android 10 中,已暂停的应用无法播放音频。

HTTPS 连接变更

如果运行 Android 10 的应用将 null 传入 setSSLSocketFactory(),则会发生 IllegalArgumentException。在以前的版本中,将 null 传入 setSSLSocketFactory() 的效果与传入当前默认工厂的效果相同。

android.preference 库已弃用

从 Android 10 开始,android.preference 库已弃用。开发者应改用 AndroidX preference 库,该库是 Android Jetpack 的一部分。如需帮助迁移和开发的其他资源,请查看更新的设置指南以及我们的公共示例应用参考文档

ZIP 文件实用程序库变更

Android 10 引入了对 java.util.zip 包中处理 ZIP 文件的类的以下变更。这些变更使库在 Android 和其他使用 java.util.zip 的平台之间的行为更加一致。

Inflater

在以前的版本中,Inflater 类中的某些方法在调用 end() 后被调用时会抛出 IllegalStateException。在 Android 10 中,这些方法改为抛出 NullPointerException

ZipFile

在 Android 10 及更高版本中,ZipFile 的构造函数(接受 FileintCharset 类型的参数)在提供的 ZIP 文件不包含任何文件时,不会抛出 ZipException

ZipOutputStream

在 Android 10 及更高版本中,ZipOutputStream 中的 finish() 方法在尝试为不包含任何文件的 ZIP 文件写入输出流时,不会抛出 ZipException

相机变更

许多使用相机的应用都假定,如果设备处于纵向配置,则物理设备也处于纵向方向,如相机方向中所述。这在过去是一个安全的假设,但随着折叠屏等可用外形规格的扩展,情况发生了变化。在这些设备上,这种假设可能导致相机取景器显示不正确旋转或缩放(或两者兼有)。

以 API 级别 24 或更高版本为目标平台的应用应明确设置 android:resizeableActivity 并提供必要的功能来处理多窗口操作。

电池使用情况跟踪

从 Android 10 开始,SystemHealthManager 会在设备在发生重大充电事件后拔下电源时重置其电池使用情况统计信息。广义上讲,重大充电事件是指:设备已充满电,或设备电量从大部分耗尽变为大部分充满。

在 Android 10 之前,只要设备拔下电源,无论电池电量变化多小,电池使用情况统计信息都会重置。

Android Beam 弃用

在 Android 10 中,我们正式弃用 Android Beam,这是一项用于通过近场通信 (NFC) 在设备之间发起数据共享的旧功能。我们还在弃用一些相关的 NFC API。Android Beam 仍可选择性地提供给希望使用的设备制造商合作伙伴,但它不再处于积极开发中。然而,Android 将继续支持其他 NFC 功能和 API,并且像从标签读取和支付等用例将继续按预期工作。

java.math.BigDecimal.stripTrailingZeros() 行为变更

BigDecimal.stripTrailingZeros() 不再将末尾零作为特殊情况保留,如果输入值为零。

java.util.regex.Matcher 和 Pattern 行为变更

当输入开头存在零宽度匹配时,split() 的结果已更改,不再以空 String ("") 开头。这也会影响 String.split()。例如,"x".split("") 现在返回 {"x"},而在旧版 Android 上它返回 {"", "x"}"aardvark".split("(?=a)" 现在返回 {"a", "ardv", "ark"},而不是 {"", "a", "ardv", "ark"}

对无效参数的异常行为也得到了改进:

  • appendReplacement(StringBuffer, String) 如果替换 String 以单独的反斜杠(非法)结尾,现在会抛出 IllegalArgumentException 而不是 IndexOutOfBoundsException。如果替换 String$ 结尾,现在也会抛出相同的异常。以前,在这种情况下不会抛出异常。
  • 如果 replaceFirst(null) 抛出 NullPointerException,它将不再在 Matcher 上调用 reset()。现在,如果没有匹配项,也会抛出 NullPointerException。以前,仅在有匹配项时才抛出此异常。
  • 如果组索引超出范围,start(int group)end(int group)group(int group) 现在会抛出更通用的 IndexOutOfBoundsException。以前,这些方法会抛出 ArrayIndexOutOfBoundsException

GradientDrawable 的默认角度现在是 TOP_BOTTOM

在 Android 10 中,如果您在 XML 中定义 GradientDrawable 并且不提供角度测量值,则渐变方向默认为 TOP_BOTTOM。这与以前的 Android 版本不同,在以前的版本中,默认值为 LEFT_RIGHT

作为一种解决方法,如果您更新到最新版本的 AAPT2,如果未指定角度测量值,该工具将为旧版应用设置角度测量值为 0。

使用默认 SUID 的序列化对象日志记录

从 Android 7.0(API 级别 24)开始,平台对可序列化对象的默认 serialVersionUID 进行了修复。此修复不影响目标平台为 API 级别 23 或更低的应用。

从 Android 10 开始,如果应用目标平台为 API 级别 23 或更低,并且依赖旧的、不正确的默认 serialVersionUID,系统会记录警告并建议代码修复。

具体来说,如果以下所有条件都为真,系统会记录警告:

  • 应用目标平台为 API 级别 23 或更低。
  • 某个类被序列化。
  • 序列化类使用默认的 serialVersionUID,而不是显式设置 serialVersionUID
  • 默认的 serialVersionUID 与应用目标平台为 API 级别 24 或更高时 serialVersionUID 的值不同。

此警告针对每个受影响的类记录一次。警告消息包含建议的修复,即显式将 serialVersionUID 设置为如果应用目标平台为 API 级别 24 或更高时将计算出的默认值。通过使用该修复,您可以确保如果该类中的对象在目标平台为 API 级别 23 或更低的应用上序列化,该对象将被目标平台为 24 或更高的应用正确读取,反之亦然。

java.io.FileChannel.map() 变更

从 Android 10 开始,FileChannel.map() 不支持非标准文件,例如 /dev/zero,其大小不能使用 truncate() 更改。以前的 Android 版本会吞噬 truncate() 返回的 errno,但 Android 10 会抛出 IOException。如果您需要旧行为,则必须使用原生代码。