行为变更:所有应用

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

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

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

  • 在后台获取设备位置信息
  • 从后台启动 Activity
  • 联系人亲密程度信息
  • 随机分配 MAC 地址
  • 相机元数据
  • 权限模型

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

限制非 SDK 接口

为了帮助确保应用的稳定性和兼容性,Android 平台从 Android 9(API 级别 28)开始限制应用能使用的非 SDK 接口。Android 10 包含更新后的受限制非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试)。我们的目标是在限制使用非 SDK 接口之前确保有可用的公开替代方案。

如果您的应用不以 Android 10(API 级别 29)为目标平台,其中一些变更可能不会立即对您产生影响。然而,虽然您目前仍可以使用一些非 SDK 接口(具体取决于应用的目标 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 连接启用 TLS 1.3。以下是有关 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 行为变更和改进

当 TLS 服务器在 TLS 握手中发送证书请求消息时,某些浏览器(如 Google Chrome)允许用户选择证书。从 Android 10 开始,KeyChain 对象会在调用 KeyChain.choosePrivateKeyAlias() 时信任颁发机构和密钥规范参数,以向用户显示证书选择提示。需要注意的是,此提示不包含不符合服务器规范的选项。

如果没有可用的用户可选证书(当没有与服务器规范匹配的证书或设备没有安装任何证书时,便会出现这种情况),则完全不会出现证书选择提示。

此外,在 Android 10 或更高版本上,无需具备设备屏幕锁定功能,就能将密钥或 CA 证书导入 KeyChain 对象中。

其他 TLS 和加密更改

Android 10 中引入的 TLS 和加密库方面的一些细小变更包括:

  • AES/GCM/NoPadding 和 ChaCha20/Poly1305/NoPadding 加密会从 getOutputSize() 中返回更准确的缓冲区空间。
  • 使用 TLS 1.2 或更高版本的最高协议在尝试连接时会忽略 TLS_FALLBACK_SCSV 加密套件。由于 TLS 服务器实现方面的改进,我们不建议尝试 TLS 外部回退。不过,我们建议依赖于 TLS 版本协商。
  • ChaCha20-Poly1305 是 ChaCha20/Poly1305/NoPadding 的别名。
  • 带有尾随点的主机名不属于有效的 SNI 主机名。
  • 为证书响应选择签名密钥时,将遵循 CertificateRequest 中的 supported_signature_algorithms 扩展。
  • 不透明的签名密钥(如 Android 密钥库中的密钥)可在 TLS 中与 RSA-PSS 签名一起使用。

WLAN 直连广播

在 Android 10 上,以下与 WLAN 直连相关的广播不具有粘性:

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

WLAN 感知功能

Android 10 新增了对使用 Wi-Fi Aware 数据路径轻松创建 TCP/UDP 套接字的支持。如需创建连接到 ServerSocket 的 TCP/UDP 套接字,客户端设备需要知道服务器的 IPv6 地址和端口。以前,这需要通过带外通信(例如使用 BT 或 Wi-Fi Aware 第 2 层消息传递)或使用其他协议(例如 mDNS)进行带内发现。在 Android 10 中,这些信息可以在网络设置过程中进行传递。

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

  • 初始化 ServerSocket,并设置或获取要使用的端口。
  • 将端口信息指定为 WLAN 感知网络请求的一部分。

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

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 版本)设备上运行的应用无法获得 SYSTEM_ALERT_WINDOW 权限。这是因为绘制叠加窗口会占用过多内存,这对低内存 Android 设备的性能尤其不利。

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

如果 Go 设备上的应用发送包含 ACTION_MANAGE_OVERLAY_PERMISSION 操作的 intent,系统会自动拒绝该请求,并将用户转到设置屏幕,该屏幕会显示由于该权限会降低设备运行速度,因此不允许授予该权限。如果 Go 设备上的应用调用 Settings.canDrawOverlays(),该方法始终返回 false。同样,这些限制不适用于在设备升级到 Android 10 之前已获得 SYSTEM_ALERT_WINDOW 权限的应用。

关于以旧版 Android 系统为目标平台的应用的警告

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

由于 Google Play 的目标 API 要求,用户只有在运行最近未更新的应用时才会看到这些警告。对于通过其他商店分发的应用,类似的目标 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.preference 库自 Android 10 起已废弃。 开发者应改用 AndroidX Preference 库(Android Jetpack 的一部分)。如需获取有助于迁移和开发的其他资源,请查看更新后的设置指南以及我们的公开示例应用和参考文档。

ZIP 文件实用程序库变更

Android 10 对 java.util.zip 软件包(用于处理 ZIP 文件)中的类进行了以下更改。这些更改使该库在 Android 和其他使用 java.util.zip 的平台上的行为更加一致。

Inflater

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

ZipFile

在 Android 10 及更高版本中,如果提供的 ZIP 文件不包含任何文件,则接受 File、int 和 Charset 类型参数的 ZipFile 构造函数不会抛出 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"}。

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

  • 如果替换 String 以孤立的反斜杠结尾(这是非法操作),appendReplacement(StringBuffer, String) 现在会抛出 IllegalArgumentException,而不是 IndexOutOfBoundsException。现在,如果替换 String 以 $ 结尾,则会抛出相同的异常。之前,在这种情况下不会抛出异常。
  • 如果 Matcher 抛出 NullPointerException,replaceFirst(null) 不再对其调用 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。如果您需要旧版行为,则必须使用原生代码。