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 对您的应用不再有任何影响,因为它现在是私有的隐式广播。相反,您应该使用 ShortcutManager 类的 requestPinShortcut() 方法创建一个应用快捷方式。
  • intent ACTION_CREATE_SHORTCUT 现在可以创建您使用 ShortcutManager 类管理的应用程序快捷方式。此 intent 还可以创建不与 ShortcutManager 交互的传统启动器快捷方式。以前,此 intent 只能创建传统启动器快捷方式。
  • 使用 requestPinShortcut() 创建的快捷方式以及在处理 ACTION_CREATE_SHORTCUT intent 的 Activity 中创建的快捷方式现在是功能齐全的应用程序快捷方式。因此,应用现在可以使用 ShortcutManager 中的方法更新它们。
  • 传统快捷方式保留了其在之前 Android 版本中的功能,但您必须在应用中手动将其转换为应用程序快捷方式。

要了解有关应用快捷方式变更的更多信息,请参阅固定快捷方式和微件功能指南。

语言区域和国际化

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

Locale 参数指定的 displayScript 值不可用时,Locale.getDisplayScript(Locale) 也会回退到 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 应用在 ChromeOS 和其他大型外形规格设备(例如平板电脑)上出现,我们看到 Android 应用中键盘导航的使用再次兴起。在 Android 8.0(API 级别 26)中,我们重新处理了将键盘用作导航输入设备的问题,从而形成了更可靠、更可预测的基于箭头和 Tab 键的导航模型。

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

  • 如果您未定义 View 对象的任何焦点状态颜色(无论是前景色还是背景 drawable),框架现在会为该 View 设置一个默认焦点高亮颜色。此焦点高亮是一个基于活动主题的涟漪 drawable。

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

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

此外,Android 8.0 中的所有工具栏元素都自动是键盘导航簇,这使得用户更容易作为一个整体进出每个工具栏。

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

Web 表单自动填充

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

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 行中。
    • 系统不再将隧道化请求中的 user-agent 和 proxy-authorization 标头发送到代理服务器。

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

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

  • 如果先前执行的 connect() 方法失败,则 send(java.net.DatagramPacket) 方法会抛出 SocketException。
    • 如果存在内部错误,DatagramSocket.connect() 会设置一个 pendingSocketException。在 Android 8.0 之前,随后的 recv() 调用会抛出 SocketException,即使 send() 调用会成功。为了一致性,现在这两个调用都会抛出 SocketException。
  • InetAddress.isReachable() 会尝试 ICMP,然后回退到 TCP Echo 协议。
    • 一些阻止端口 7(TCP Echo)的主机,例如 google.com,如果它们接受 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 服务和 Advertising ID 的设备上,您必须使用 Advertising ID。Advertising ID 是一个简单的、标准的系统,用于通过唯一、用户可重置的广告 ID 来创收。它由 Google Play 服务提供。

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

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

未捕获异常的日志记录

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

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

findViewById() 签名变更

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

  • 这可能会导致现有代码的返回类型不明确,例如,如果同时存在接收 findViewById() 调用结果的 someMethod(View)someMethod(TextView)
  • 使用 Java 8 源语言时,当返回类型无限制时(例如,assertNotNull(findViewById(...)).someViewMethod())),这需要显式转换为 View
  • 非 final findViewById() 方法(例如 Activity.findViewById())的重写需要更新其返回类型。

联系人提供商使用统计信息变更

在之前版本的 Android 中,Contacts Provider 组件允许开发者获取每个联系人的使用数据。此使用数据公开了与联系人相关的每个电子邮件地址和电话号码的信息,包括联系人的联系次数和最后一次联系时间。请求 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) 对于以 Android 8.0 为目标平台的应用的行为。

这些 API 现在要求在所有 Uri 的 Authority 中定义一个有效的 ContentProvider。定义一个具有相关权限的有效 ContentProvider 将有助于您的应用抵御恶意应用的内容更改,并防止您将潜在的私有数据泄露给恶意应用。

View 焦点

可点击的 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 的行为就像同一资料组中没有安装任何其他资料的应用一样。与以前一样,尝试访问不相关的资料会导致 SecurityExceptions。

权限

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

对于以 Android 8.0 为目标平台的应用,此行为已得到纠正。应用只被授予其明确请求的权限。但是,一旦用户授予应用权限,随后对该权限组中权限的所有请求都会自动获得授予。

例如,假设应用在其清单中同时列出了 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE 并且用户授予了它。如果应用以 API 级别 25 或更低版本为目标平台,系统也会同时授予 WRITE_EXTERNAL_STORAGE,因为它属于同一个 STORAGE 权限组并且也在清单中注册。如果应用以 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 Activity 中媒体按钮的处理方式没有改变:前台 Activity 仍然优先处理媒体按钮事件。
    2. 如果前台 Activity 未处理媒体按钮事件,系统会将事件路由到最近在本地播放音频的应用。媒体会话的活动状态、标志和播放状态在确定哪个应用接收媒体按钮事件时不予考虑。
    3. 如果应用的媒体会话已释放,系统会将媒体按钮事件发送到应用的 MediaButtonReceiver(如果存在)。
    4. 对于所有其他情况,系统会丢弃媒体按钮事件。

原生库

在以 Android 8.0(API 级别 26)为目标平台的应用中,如果原生库包含任何可写且可执行的加载段,则无法加载。某些应用可能会因为此更改而停止工作,如果其原生库的加载段不正确。这是一项安全强化措施。

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

链接器更改与应用的目标 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);
      }
    }

    在大多数情况下,您还可以使用一个委托给不同默认实现的实现来覆盖 List.sort(),具体取决于 API 级别。例如:

    @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() 会抛出 ConcurrentModificationException,如果排序是通过调用 List.sort() 完成的。而 Collections.sort() 不会抛出异常。

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

类加载行为

Android 8.0(API 级别 26)检查以确保类加载器在加载新类时不会破坏运行时的假设。这些检查在通过 Java (forName())、Dalvik 字节码或 JNI 引用类时都会执行。平台不会拦截从 Java 到 loadClass() 方法的直接调用,也不会检查此类调用的结果。此行为不应影响运行良好的类加载器的功能。

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

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

这与 JNI 调用 FindClass() 不同,后者中的 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)的平台版本中,违反这些假设可能导致多次定义同一个类、由于类混淆引起的堆损坏以及其他不良影响。