Android 8.0 行为变更

除了新的功能和特性之外,Android 8.0(API 级别 26)还包含各种系统和 API 行为变更。本文档重点介绍了一些您应该了解并在应用中考虑的关键变更。

大多数这些变更会影响所有应用,而不管它们的目标 Android 版本是什么。但是,一些变更仅影响目标 Android 8.0 的应用。为了最大程度地提高清晰度,此页面分为两个部分:所有应用的变更目标 Android 8.0 的应用的变更

所有应用的变更

这些行为变更适用于所有应用当它们在 Android 8.0(API 级别 26)平台上运行时,无论它们的目标 API 级别是什么。所有开发者都应查看这些变更,并在适用的情况下修改其应用以正确支持它们。

后台执行限制

作为 Android 8.0(API 级别 26)引入的改进电池续航时间的变更之一,当您的应用进入缓存状态且没有活动的组件时,系统会释放应用持有的所有唤醒锁。

此外,为了提高设备性能,系统会限制不在前台运行的应用的某些行为。具体来说:

  • 在后台运行的应用现在对如何自由访问后台服务有限制。
  • 应用无法使用其清单注册大多数隐式广播(即,并非专门针对该应用的广播)。

默认情况下,这些限制仅适用于目标为 O 的应用。但是,用户可以从设置屏幕为任何应用启用这些限制,即使该应用尚未目标为 O。

Android 8.0(API 级别 26)还包含以下对特定方法的更改:

  • 如果目标 Android 8.0 的应用在不允许创建后台服务的场景中尝试使用该方法,则startService()方法现在会抛出一个IllegalStateException
  • 新的Context.startForegroundService()方法启动前台服务。即使应用位于后台,系统也允许应用调用Context.startForegroundService()。但是,应用必须在服务创建后的五秒钟内调用该服务的startForeground()方法。

有关更多信息,请参阅后台执行限制

Android 后台位置限制

为了保持电池续航时间、用户体验和系统健康,在运行 Android 8.0 的设备上使用时,后台应用接收位置更新的频率会降低。此行为变更会影响所有接收位置更新的应用,包括 Google Play 服务。

这些更改会影响以下 API:

  • 融合位置提供程序 (FLP)
  • 地理围栏
  • GNSS 测量
  • 位置管理器
  • Wi-Fi 管理器

为了确保您的应用按预期运行,请完成以下步骤:

  • 查看应用的逻辑并确保您正在使用最新的位置 API。
  • 测试您的应用是否在每个用例中都表现出您期望的行为。
  • 考虑使用融合位置提供程序 (FLP)或地理围栏来处理依赖于用户当前位置的用例。

有关这些变更的更多信息,请参阅后台位置限制

应用快捷方式

Android 8.0(API 级别 26)包含以下对应用快捷方式的更改:

  • com.android.launcher.action.INSTALL_SHORTCUT广播不再对您的应用有任何影响,因为它现在是私有的隐式广播。相反,您应该使用requestPinShortcut()方法(来自ShortcutManager类)来创建应用快捷方式。
  • ACTION_CREATE_SHORTCUT意图现在可以创建您可以使用ShortcutManager类管理的应用快捷方式。此意图还可以创建不与ShortcutManager交互的旧版启动器快捷方式。以前,此意图只能创建旧版启动器快捷方式。
  • 使用requestPinShortcut()创建的快捷方式以及在处理ACTION_CREATE_SHORTCUT意图的活动中创建的快捷方式现在都是成熟的应用快捷方式。因此,应用现在可以使用ShortcutManager中的方法更新它们。

  • 旧版快捷方式保留了 Android 之前版本的功能,但您必须在应用中手动将其转换为应用快捷方式。

要详细了解应用快捷方式的更改,请参阅固定快捷方式和小部件功能指南。

语言环境和国际化

Android 7.0(API 级别 24)引入了指定默认类别语言环境的概念,但某些 API 继续使用通用的Locale.getDefault()方法(无参数),而应该使用默认的DISPLAY类别语言环境。在 Android 8.0(API 级别 26)中,以下方法现在使用Locale.getDefault(Category.DISPLAY)而不是Locale.getDefault()

Locale.getDisplayScript(Locale)当为Locale参数指定的displayScript值不可用时,也会回退到Locale.getDefault()

其他与语言环境和国际化相关的更改如下

  • 调用Currency.getDisplayName(null)会抛出NullPointerException,与记录的行为匹配。
  • 时区名称解析已更改。以前,Android 设备使用在启动时采样的系统时钟值来缓存用于解析日期时间的时区名称。因此,如果系统时钟在启动时或其他更罕见的情况下错误,则解析可能会受到负面影响。

    现在,在常见情况下,解析逻辑在解析时区名称时使用 ICU 和当前系统时钟值。此更改提供了更正确的结果,当您的应用使用SimpleDateFormat之类的类时,这可能与早期 Android 版本不同。

  • Android 8.0(API 级别 26)将 ICU 的版本更新为版本 58。

警报窗口

如果应用使用SYSTEM_ALERT_WINDOW权限并使用以下窗口类型之一尝试在其他应用和系统窗口上方显示警报窗口

…那么这些窗口始终显示在使用TYPE_APPLICATION_OVERLAY窗口类型的窗口下方。如果应用以 Android 8.0(API 级别 26)为目标,则应用使用TYPE_APPLICATION_OVERLAY窗口类型显示警报窗口。

有关更多信息,请参阅警报窗口的常见窗口类型部分,该部分位于以 Android 8.0 为目标的应用的行为更改中。

输入和导航

随着 Android 应用在 Chrome OS 和其他大型外形规格(如平板电脑)上的出现,我们看到了 Android 应用中键盘导航使用的复苏。在 Android 8.0(API 级别 26)中,我们重新解决了将键盘用作导航输入设备的问题,从而为基于箭头和选项卡的导航提供了一个更可靠、更可预测的模型。

特别是,我们对元素焦点行为进行了以下更改

  • 如果您尚未为View对象(其前景色或背景可绘制对象)定义任何焦点状态颜色,则框架现在会为View设置默认焦点高亮颜色。此焦点高亮显示是基于活动主题的波纹可绘制对象。

    如果您不希望View对象在获得焦点时使用此默认高亮显示,请在包含View的布局 XML 文件中将android:defaultFocusHighlightEnabled属性设置为false,或在应用的 UI 逻辑中将false传递给setDefaultFocusHighlightEnabled()

  • 要测试键盘输入如何影响 UI 元素焦点,您可以启用“绘图 > 显示布局边界”开发者选项。在 Android 8.0 中,此选项会在当前具有焦点的元素上显示“X”图标。

此外,Android 8.0 中的所有工具栏元素都自动成为键盘导航群集,使用户更容易整体进入和退出每个工具栏。

要详细了解如何改善对应用中键盘导航的支持,请阅读支持键盘导航指南。

Web 表单自动填充

现在 Android 自动填充框架提供了对自动填充功能的内置支持,以下与WebView对象相关的方法已针对安装在运行 Android 8.0(API 级别 26)的设备上的应用进行了更改

WebSettings
WebViewDatabase
  • 调用clearFormData()不再有任何效果。
  • hasFormData()方法现在返回false。以前,当表单包含数据时,此方法返回true

辅助功能

Android 8.0(API 级别 26)包含以下辅助功能更改

  • 辅助功能框架现在将所有双击手势转换为ACTION_CLICK操作。此更改允许 TalkBack 的行为更像其他辅助功能服务。

    如果应用的View对象使用自定义触摸处理,则应验证它们是否仍与 TalkBack 配合使用。您可能只需要注册View对象使用的点击处理程序。如果 TalkBack 仍然无法识别对这些View对象执行的手势,请覆盖performAccessibilityAction()

  • 辅助功能服务现在了解应用的TextView对象中所有ClickableSpan实例。

要详细了解如何使您的应用更易于访问,请参阅辅助功能

网络和 HTTP(S) 连接

Android 8.0(API 级别 26)包含以下网络和 HTTP(S) 连接行为更改

  • 没有正文的 OPTIONS 请求具有Content-Length: 0标头。以前它们没有Content-Length标头。
  • HttpURLConnection 通过在主机或授权名称后附加斜杠来规范化包含空路径的 URL。例如,它将http://example.com转换为http://example.com/
  • 通过 ProxySelector.setDefault() 设置的自定义代理选择器仅针对请求 URL 的地址(方案、主机和端口)。因此,代理选择可能仅基于这些值。传递给自定义代理选择器的 URL 不包括请求 URL 的路径、查询参数或片段。
  • URI 无法包含空标签。

    以前,平台支持一种解决方法来接受主机名中的空标签,这是一种非法的 URI 使用方式。此解决方法是为了与旧版本的 libcore 兼容。不正确地使用 API 的开发者将看到一条 ADB 消息:“URI example..com 在主机名中包含空标签。这格式错误,在将来的 Android 版本中将不被接受。”Android 8.0 删除了此解决方法;系统会为格式错误的 URI 返回 null。

  • Android 8.0 的 HttpsURLConnection 实现不执行不安全的 TLS/SSL 协议版本回退。
  • 隧道 HTTP(S) 连接的处理方式已更改,如下所示
    • 当通过连接隧道传输 HTTPS 连接时,系统会在发送此信息到中间服务器时正确地在 Host 行中放置端口号(:443)。以前,端口号仅出现在 CONNECT 行中。
    • 系统不再从隧道请求发送用户代理和代理授权标头到代理服务器。

      在设置隧道时,系统不再向代理发送隧道 Http(s)URLConnection 上的代理授权标头。相反,系统会生成一个代理授权标头,并在代理响应初始请求时发送 HTTP 407 时将其发送到代理。

      类似地,系统不再将用户代理标头从隧道请求复制到设置隧道的代理请求。相反,库会为该请求生成一个用户代理标头。

  • 如果之前执行的 connect() 方法失败,则send(java.net.DatagramPacket)方法会抛出 SocketException。
    • DatagramSocket.connect() 如果存在内部错误,则会设置 pendingSocketException。在 Android 8.0 之前,随后的 recv() 调用会抛出 SocketException,即使 send() 调用已成功。为了保持一致性,这两个调用现在都抛出 SocketException。
  • InetAddress.isReachable() 在回退到 TCP Echo 协议之前尝试 ICMP。
    • 如果某些主机(例如 google.com)阻止端口 7(TCP Echo),则如果它们接受 ICMP Echo 协议,现在可能会变得可访问。
    • 对于真正无法访问的主机,此更改意味着在调用返回之前花费的时间是原来的两倍。

蓝牙

Android 8.0(API 级别 26)对ScanRecord.getBytes()方法检索的数据长度进行了以下更改

  • getBytes() 方法不对接收到的字节数做任何假设。因此,应用不应依赖于返回的字节数的最小值或最大值。相反,它们应该评估结果数组的长度。
  • 兼容蓝牙 5 的设备可能会返回超过之前最大值(约 60 字节)的数据长度。
  • 如果远程设备不提供扫描响应,也可能会返回少于 60 个字节的数据。

无缝连接

Android 8.0(API 级别 26)对 Wi-Fi 设置进行了一些改进,以便于选择提供最佳用户体验的 Wi-Fi 网络。具体更改包括

  • 稳定性和可靠性改进。
  • 更直观的 UI。
  • 一个集成的 Wi-Fi 首选项菜单。
  • 在兼容设备上,当附近有高质量的已保存网络时,自动激活 Wi-Fi。

安全

Android 8.0 包含以下与安全相关的更改

  • 平台不再支持 SSLv3。
  • 当建立到错误实现 TLS 协议版本协商的服务器的 HTTPS 连接时,HttpsURLConnection 不再尝试回退到早期 TLS 协议版本并重试的解决方法。
  • Android 8.0(API 级别 26)对所有应用应用了安全计算(SECCOMP)过滤器。允许的系统调用的列表仅限于通过 bionic 公开的那些系统调用。尽管为了向后兼容性提供了其他几个系统调用,但我们建议不要使用它们。
  • 您的应用的 WebView 对象现在在多进程模式下运行。Web 内容在与包含应用的进程分开的隔离进程中处理,以增强安全性。
  • 您不能再假设 APK 驻留在名称以 -1 或 -2 结尾的目录中。应用应使用 sourceDir 获取目录,而不是直接依赖于目录格式。
  • 有关与使用原生库相关的安全增强功能的信息,请参阅 原生库

此外,Android 8.0(API 级别 26)引入了以下与从未知来源安装未知应用相关的更改

有关安装未知应用的其他详细信息,请参阅 未知应用安装权限 指南。

有关使您的应用更安全的其他指南,请参阅 Android 开发人员的安全指南

隐私

Android 8.0(API 级别 26)对平台做出了以下与隐私相关的更改。

  • 平台现在以不同的方式处理标识符。
    • 对于在 OTA 升级到 Android 8.0(API 级别 26)版本之前安装的应用,ANDROID_ID 的值保持不变,除非卸载并在 OTA 后重新安装。为了在 OTA 后跨卸载保留值,开发者可以使用 键值备份 将旧值和新值关联起来。
    • 对于在运行 Android 8.0 的设备上安装的应用,ANDROID_ID 的值现在按应用签名密钥以及按用户进行范围限定。 ANDROID_ID 的值对于应用签名密钥、用户和设备的每个组合都是唯一的。因此,在同一设备上运行的不同签名密钥的应用不再看到相同的 Android ID(即使对于同一用户也是如此)。
    • 只要签名密钥相同(并且应用不是在 OTA 升级到 Android 8.0 版本之前安装的),ANDROID_ID 的值在软件包卸载或重新安装时不会更改。
    • 即使系统更新导致软件包签名密钥更改,ANDROID_ID 的值也不会更改。
    • 在出厂预装 Google Play 服务和广告 ID 的设备上,您必须使用 广告 ID。广告 ID 是一个简单、标准的应用获利系统,它是一个用于广告的唯一、用户可重置的 ID。它由 Google Play 服务提供。

      其他设备制造商应继续提供 ANDROID_ID

  • 查询 net.hostname 系统属性会产生空结果。

未捕获异常的日志记录

如果应用安装了 Thread.UncaughtExceptionHandler,并且该处理程序没有调用默认的 Thread.UncaughtExceptionHandler,则当发生未捕获异常时,系统不会终止应用。从 Android 8.0(API 级别 26)开始,系统在这种情况下会记录异常堆栈跟踪;在平台的早期版本中,系统不会记录异常堆栈跟踪。

我们建议自定义 Thread.UncaughtExceptionHandler 实现始终调用默认处理程序;遵循此建议的应用不受 Android 8.0 中更改的影响。

findViewById() 签名更改

现在,所有 findViewById() 方法的实例都返回 <T extends View> T 而不是 View。此更改具有以下影响

  • 这可能会导致现有代码现在具有不明确的返回类型,例如,如果同时存在 someMethod(View)someMethod(TextView),并且它们都接收对 findViewById() 的调用的结果。
  • 在使用 Java 8 源语言时,当返回类型不受约束时(例如,assertNotNull(findViewById(...)).someViewMethod())),这需要显式转换为 View
  • 非最终 findViewById() 方法(例如,Activity.findViewById())的覆盖需要更新其返回类型。

联系人提供程序使用情况统计更改

在以前的 Android 版本中,联系人提供程序组件允许开发者获取每个联系人的使用情况数据。此使用情况数据会公开与联系人关联的每个电子邮件地址和每个电话号码的信息,包括联系该联系人的次数以及上次联系该联系人的时间。请求 READ_CONTACTS 权限的应用可以读取此数据。

如果应用请求 READ_CONTACTS 权限,则仍可以读取此数据。在 Android 8.0(API 级别 26)及更高版本中,对使用情况数据的查询返回近似值而不是精确值。Android 系统在内部维护精确值,因此此更改不会影响自动完成 API。

此行为更改会影响以下查询参数

集合处理

AbstractCollection.removeAll()AbstractCollection.retainAll() 现在始终抛出 NullPointerException;以前,当集合为空时不会抛出 NullPointerException。此更改使行为与文档一致。

Android 企业版

Android 8.0(API 级别 26)更改了一些 API 和企业应用(包括设备策略控制器 (DPC))的功能的行为。更改包括

  • 帮助应用支持完全托管设备上的工作配置文件的新行为。
  • 对系统更新处理、应用验证和身份验证的更改,以提高设备和系统的完整性。
  • 对预配、通知、最近使用的应用屏幕和始终在线 VPN 的用户体验的改进。

要查看 Android 8.0(API 级别 26)中的所有企业更改,并了解它们可能会如何影响您的应用,请阅读 企业版 Android

以 Android 8.0 为目标的应用

这些行为更改仅适用于以 Android 8.0(API 级别 26)或更高版本为目标的应用。针对 Android 8.0 编译或将 targetSdkVersion 设置为 Android 8.0 或更高版本的应用必须修改其应用以正确支持这些行为(在适用的情况下)。

警报窗口

使用 SYSTEM_ALERT_WINDOW 权限的应用不能再使用以下窗口类型在其他应用和系统窗口上方显示警报窗口

相反,应用必须使用名为 TYPE_APPLICATION_OVERLAY 的新窗口类型。

当使用 TYPE_APPLICATION_OVERLAY 窗口类型为您的应用显示警报窗口时,请牢记新窗口类型的以下特性

  • 应用的警报窗口始终显示在关键系统窗口(如状态栏和 IME)下方。
  • 系统可以移动或调整使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口的大小,以改善屏幕显示效果。
  • 通过打开通知栏,用户可以访问设置以阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的警报窗口。

内容更改通知

Android 8.0(API 级别 26)更改了 ContentResolver.notifyChange()registerContentObserver(Uri, boolean, ContentObserver) 对目标 API 级别为 Android 8.0 的应用的行为方式。

这些 API 现在要求在所有 Uri 中的权限都定义了有效的 ContentProvider。定义具有相关权限的有效 ContentProvider 将有助于保护您的应用免受恶意应用内容更改的影响,并防止您将潜在的私有数据泄露给恶意应用。

视图焦点

可点击的 View 对象现在也默认可聚焦。如果您希望 View 对象可点击但不可聚焦,请在包含 View 的布局 XML 文件中将 android:focusable 属性设置为 false,或在应用的 UI 逻辑中将 false 传递给 setFocusable()

浏览器检测中的用户代理匹配

Android 8.0(API 级别 26)及更高版本包含构建标识符字符串 OPR。某些模式匹配可能会导致浏览器检测逻辑将非 Opera 浏览器误识别为 Opera。此类模式匹配的一个示例可能是

if(p.match(/OPR/)){k="Opera";c=p.match(/OPR\/(\d+.\d+)/);n=new Ext.Version(c[1])}

为了避免此类误识别引起的问题,请使用除 OPR 之外的字符串作为 Opera 浏览器的模式匹配。

安全

以下更改会影响 Android 8.0(API 级别 26)中的安全性

  • 如果应用的网络安全配置 选择退出 支持明文流量,则应用的 WebView 对象无法通过 HTTP 访问网站。每个 WebView 对象必须改用 HTTPS。
  • 已删除允许未知来源系统设置;取而代之的是,安装未知应用权限管理来自未知来源的未知应用安装。要了解有关此新权限的更多信息,请参阅 未知应用安装权限 指南。

有关使您的应用更安全的其他指南,请参阅 Android 开发人员的安全指南

帐户访问和可发现性

在 Android 8.0(API 级别 26)中,应用无法再访问用户帐户,除非身份验证器拥有这些帐户或用户授予该访问权限。 GET_ACCOUNTS 权限已不再足够。要获得对帐户的访问权限,应用应使用 AccountManager.newChooseAccountIntent() 或特定于身份验证器的方法。获得对帐户的访问权限后,应用可以调用 AccountManager.getAccounts() 来访问它们。

Android 8.0 不推荐使用 LOGIN_ACCOUNTS_CHANGED_ACTION。应用应改用 addOnAccountsUpdatedListener() 在运行时获取有关帐户的更新。

有关为帐户访问和可发现性添加的新 API 和方法的信息,请参阅本文档“新 API”部分中的 帐户访问和可发现性

隐私

以下更改会影响 Android 8.0(API 级别 26)中的隐私。

  • 系统属性 net.dns1net.dns2net.dns3net.dns4 已不再可用,此更改提高了平台的隐私性。
  • 要获取网络信息(例如 DNS 服务器),具有 ACCESS_NETWORK_STATE 权限的应用可以注册 NetworkRequestNetworkCallback 对象。这些类在 Android 5.0(API 级别 21)及更高版本中可用。
  • Build.SERIAL 已弃用。需要了解硬件序列号的应用应改用新的 Build.getSerial() 方法,该方法需要 READ_PHONE_STATE 权限。
  • LauncherApps API 不再允许工作配置文件应用获取有关主配置文件的信息。当用户处于工作配置文件中时,LauncherApps API 的行为就像同一配置文件组中其他配置文件中未安装任何应用一样。与以前一样,尝试访问不相关的配置文件会导致 SecurityException。

权限

在 Android 8.0(API 级别 26)之前,如果应用在运行时请求权限并且授予了该权限,则系统还会错误地向应用授予属于同一权限组并在清单中注册的其他权限。

对于目标 API 级别为 Android 8.0 的应用,此行为已得到纠正。应用仅获得其明确请求的权限。但是,一旦用户向应用授予权限,随后对该权限组中权限的所有请求都会自动获得批准。

例如,假设应用在其清单中同时列出了 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE。该应用请求 READ_EXTERNAL_STORAGE,用户授予了该请求。如果应用的目标 API 级别为 25 或更低,则系统也会同时授予 WRITE_EXTERNAL_STORAGE,因为它属于同一 STORAGE 权限组,并且也在清单中注册。如果应用的目标 API 级别为 Android 8.0(API 级别 26),则系统此时仅授予 READ_EXTERNAL_STORAGE;但是,如果应用稍后请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而无需提示用户。

媒体

  • 框架本身可以执行 自动音频降噪。在这种情况下,当另一个应用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 请求焦点时,拥有焦点的应用会降低其音量,但通常不会收到 onAudioFocusChange() 回调,也不会失去音频焦点。对于需要暂停而不是降噪的应用,可以使用新的 API 覆盖此行为。
  • 当用户接听电话时,活动媒体流会在通话期间静音。
  • 所有与音频相关的 API 应使用 AudioAttributes 而不是音频流类型来描述音频播放用例。继续仅将音频流类型用于音量控制。流类型的其他用途仍然有效(例如,已弃用的 AudioTrack 构造函数的 streamType 参数),但系统会将其记录为错误。
  • 使用 AudioTrack 时,如果应用请求足够大的音频缓冲区,则框架将尝试使用深度缓冲区输出(如果可用)。
  • 在 Android 8.0(API 级别 26)中,媒体按钮事件 的处理方式有所不同
    1. UI 活动中媒体按钮的处理方式没有改变:前台活动仍然优先处理媒体按钮事件。
    2. 如果前台活动未处理媒体按钮事件,则系统会将事件路由到最近在本地播放音频的应用。在确定哪个应用接收媒体按钮事件时,不会考虑媒体会话的活动状态、标志和播放状态。
    3. 如果应用的媒体会话已释放,则系统会将媒体按钮事件发送到应用的 MediaButtonReceiver(如果存在)。
    4. 对于所有其他情况,系统都会丢弃媒体按钮事件。

原生库

在目标 API 级别为 Android 8.0(API 级别 26)的应用中,如果原生库包含任何既可写又可执行的加载段,则该库将不再加载。如果应用的原生库具有错误的加载段,则由于此更改,某些应用可能会停止工作。这是一项安全强化措施。

有关更多信息,请参阅 可写和可执行段

链接器更改与应用的目标 API 级别相关联。如果在目标 API 级别存在链接器更改,则应用将无法加载库。如果您目标 API 级别低于发生链接器更改的 API 级别,则 logcat 会显示警告。

集合处理

在 Android 8.0(API 级别 26)中,Collections.sort()List.sort() 之上实现。在 Android 7.x(API 级别 24 和 25)中,情况正好相反:List.sort() 的默认实现调用了 Collections.sort()

此更改允许 Collections.sort() 利用优化的 List.sort() 实现,但存在以下约束条件

  • List.sort() 的实现不得调用 Collections.sort(),因为这样做会导致无限递归导致堆栈溢出。相反,如果您希望在 List 实现中使用默认行为,则应避免覆盖 sort()

    如果父类不适当地实现了 sort(),则通常可以使用基于 List.toArray()Arrays.sort()ListIterator.set() 构建的实现来覆盖 List.sort()。例如

    @Override
    public void sort(Comparator<? super E> c) {
      Object[] elements = toArray();
      Arrays.sort(elements, c);
      ListIterator<E> iterator = (ListIterator<Object>) listIterator();
      for (Object element : elements) {
        iterator.next();
        iterator.set((E) element);
      }
    }

    在大多数情况下,您还可以使用根据 API 级别委派给不同默认实现的实现来覆盖 List.sort()。例如

    @Override
    public void sort(Comparator<? super E> comparator) {
      if (Build.VERSION.SDK_INT <= 25) {
        Collections.sort(this);
      } else {
        super.sort(comparator);
      }
    }

    如果您只因为希望在所有 API 级别上都提供 sort() 方法而执行后者,请考虑为其指定一个唯一的名称(例如 sortCompat()),而不是覆盖 sort()

  • Collections.sort() 现在在调用 sort() 的 List 实现中被视为结构性修改。例如,在 Android 8.0(API 级别 26)之前的平台版本中,如果迭代 ArrayList 并在此过程中对其调用 sort(),则如果排序是通过调用 List.sort() 完成的,则会抛出 ConcurrentModificationExceptionCollections.sort() 不会抛出异常。

    此更改使平台行为更加一致:现在,两种方法都会导致 ConcurrentModificationException

类加载行为

Android 8.0(API 级别 26)会检查类加载器在加载新类时是否违反了运行时的假设。无论类是从 Java(从 forName())、Dalvik 字节码还是 JNI 中引用,都会执行这些检查。平台不会拦截从 Java 到 loadClass() 方法的直接调用,也不会检查此类调用的结果。此行为不应影响行为良好的类加载器的功能。

平台会检查类加载器返回的类的描述符是否与预期描述符匹配。如果返回的描述符不匹配,平台将抛出 NoClassDefFoundError 错误,并在异常中存储详细消息以说明差异。

平台还会检查请求的类的描述符是否有效。此检查会捕获间接加载类的 JNI 调用(例如 GetFieldID()),并将无效描述符传递给这些类。例如,签名为 java/lang/String 的字段未找到,因为该签名无效;它应该是 Ljava/lang/String;

这与对 FindClass() 的 JNI 调用不同,在该调用中,java/lang/String 是有效的完全限定名称。

Android 8.0(API 级别 26)不支持多个类加载器尝试使用相同的 DexFile 对象定义类。尝试这样做会导致 Android 运行时抛出 InternalError 错误,并显示消息“尝试使用多个类加载器注册 dex 文件 <filename>”。

DexFile API 现已弃用,强烈建议您改用平台类加载器之一,包括 PathClassLoaderBaseDexClassLoader

注意:您可以创建多个类加载器,这些类加载器引用文件系统中相同 APK 或 JAR 文件容器。这样做通常不会导致太多内存开销:如果容器中的 DEX 文件是存储的而不是压缩的,则平台可以对它们执行 mmap 操作,而不是直接提取它们。但是,如果平台必须从容器中提取 DEX 文件,则以这种方式引用 DEX 文件可能会消耗大量内存。

在 Android 中,所有类加载器都被认为是并行功能的。当多个线程争用使用相同的类加载器加载相同的类时,第一个完成操作的线程获胜,结果将用于其他线程。无论类加载器是否返回相同的类、返回不同的类或抛出异常,都会发生此行为。平台会静默忽略此类异常。

注意:在低于 Android 8.0(API 级别 26)的平台版本中,违反这些假设会导致多次定义相同的类、由于类混淆导致的堆损坏以及其他不良影响。