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:

  • Fused Location Provider (FLP)
  • 地理围栏
  • GNSS Measurements
  • Location Manager
  • Wi-Fi Manager

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

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

如需详细了解这些变更,请参阅后台位置限制

应用快捷方式

Android 8.0(API 级别 26)对应用快捷方式做出了以下变更:

  • com.android.launcher.action.INSTALL_SHORTCUT 广播不再对您的应用有任何影响,因为它现在是私有的隐式广播。您应使用 ShortcutManager 类中的 requestPinShortcut() 方法创建应用快捷方式。
  • 现在,ACTION_CREATE_SHORTCUT intent 可以创建可使用 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 对象(前景或背景可绘制对象)定义任何焦点状态颜色,框架会为 View 设置默认的焦点突出显示颜色。此焦点突出显示标志是基于 activity 主题的涟漪可绘制对象。

    如果您不希望 View 对象在接收焦点时使用此默认突出显示标志,请在包含 View 的布局 XML 文件中将 android:defaultFocusHighlightEnabled 属性设置为 false,或者将 false 传递至应用界面逻辑中的 setDefaultFocusHighlightEnabled()

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

此外,Android 8.0 中的所有工具栏元素自动组成键盘导航键区,用户可以更加轻松地导航进入和离开每个作为一个整体的工具栏。

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

网页表单自动填充

现在,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 标头。
  • Http网址Connection 在主机或授权机构名称后面附加一个斜杠,对包含空路径的网址进行标准化。例如,它会将 http://example.com 转换为 http://example.com/
  • 通过 ProxySelector.setDefault() 设置的自定义代理选择器仅针对所请求的网址(架构、主机和端口)。 因此,只能根据这些值选择代理。传递至自定义代理选择器的网址不包含所请求的网址的路径、查询参数或片段。
  • URI 不能包含空标签。

    之前,平台支持一种权宜方法,即允许主机名称中包含空白标签,但这是对 URI 的非法使用。此权宜方法只是为了确保与旧版 libcore 兼容。如果开发者错误地使用该 API,将会看到以下 ADB 消息:“URI example..com 在主机名中具有空标签。此格式不正确,将不被未来的 Android 版本所接受。” Android 8.0 移除了此权宜解决方法;系统会对格式错误的 URI 返回 null。

  • Android 8.0 在实现 Https网址Connection 时不会执行不安全的 TLS/SSL 协议版本回退。
  • 对隧道 HTTP(S) 连接处理进行了如下变更:
    • 通过连接的隧道建立 HTTPS 连接时,在将此信息发送到中间服务器时,系统会正确地将端口号 (:443) 置于主机行。之前,端口号仅出现在 CONNECT 行中。
    • 系统不再将隧道连接请求中的 user-agent 和 proxy-authorization 标头发送至代理服务器。

      在建立隧道时,系统不再将隧道 Http(s)网址Connection 中的 proxy-authorization 标头发送至代理。相反,由系统生成 proxy-authorization 标头,在代理响应初始请求发送 HTTP 407 后将其发送至此代理。

      同样地,系统不再将 user-agent 标头由隧道连接请求复制到建立隧道的代理请求。相反,库会为该请求生成用户代理标头。

  • 如果之前执行的 connect() 方法失败,send(java.net.DatagramPacket) 方法会引发 SocketException。
    • 如果存在内部错误,DatagramSocket.connect() 会引发 pendingSocketException。对于 Android 8.0 之前的版本,即使 send() 调用成功,后续的 recv() 调用也会引发 SocketException。为确保一致性,现在这两个调用均会引发 SocketException。
  • 在回退到 TCP Echo 协议之前,InetAddress.isReachable() 会尝试执行 ICMP。
    • 某些屏蔽端口 7 (TCP Echo) 的主机(例如 google.com)现在如果接受 ICMP Echo 协议,可能变为可访问状态。
    • 对于确实无法访问的主机,此项变更意味着调用需要两倍的时间才能返回结果。

蓝牙

Android 8.0(API 级别 26)对 ScanRecord.getBytes() 方法检索的数据长度做出以下变更:

  • getBytes() 方法对于所接收的字节数不作任何假定。因此,应用不应受所返回的任何最小或最大字节数的影响。相反,应用应当计算所返回数组的长度。
  • 兼容蓝牙 5 的设备返回的数据长度可能会超出之前最大约 60 个字节的限制。
  • 如果远程设备未提供扫描响应,则也可能返回少于 60 个字节的数据。

无缝连接

Android 8.0(API 级别 26)对 WLAN 设置进行了多项改进,这样可以更轻松地选择能够提供最佳用户体验的 WLAN 网络。具体更改包括:

  • 稳定性和可靠性改进。
  • 更加直观的界面。
  • 一个合并的 WLAN 首选项菜单。
  • 当附近存在优质的已保存网络时在兼容设备上自动激活 WLAN。

安全

Android 8.0 包含以下与安全性有关的变更:

  • 此平台不再支持 SSLv3。
  • 在与未正确实现 TLS 协议版本协商的服务器建立 HTTPS 连接时,HttpsURLConnection 不再尝试回退到之前的 TLS 协议版本并重试的权宜方法。
  • Android 8.0(API 级别 26)将使用安全计算 (SECCOMP) 过滤器来过滤所有应用。允许的系统调用列表仅限于通过 bionic 公开的系统调用。虽然还提供了其他几个后向兼容的系统调用,但我们不建议使用这些系统调用。
  • 现在,应用的 WebView 对象将在多进程模式下运行。网页内容在独立的进程中处理,此进程与包含应用的进程相隔离,以提高安全性。
  • 您无法再假定 APK 驻留在名称以 -1 或 -2 结尾的目录中。应用应使用 sourceDir 获取此目录,而不能直接使用目录格式。
  • 如需了解与使用原生库相关的安全性增强功能,请参阅原生库

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

如需详细了解如何安装未知应用,请参阅未知应用安装权限指南。

如需有关提升应用安全性的其他准则,请参阅面向 Android 开发者的安全性

隐私设置

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

  • 现在,平台改变了标识符的处理方式。
    • 对于在 OTA 之前安装到某个版本 Android 8.0(API 级别 26)的应用,除非在 OTA 后卸载并重新安装,否则 ANDROID_ID 的值将保持不变。如需在 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 系统属性会返回 null 结果。

记录未捕获的异常

如果某个应用安装的 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 系统内部仍然会保留精确值,因此,此变更不会影响 auto-complete 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 窗口类型显示应用的提醒窗口时,请记住新窗口类型的以下特性:

  • 应用的提醒窗口始终显示在状态栏和输入法等关键系统窗口的下面。
  • 系统可以移动使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口或调整其大小,以改善屏幕显示效果。
  • 通过打开通知栏,用户可以访问设置来阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的提醒窗口。

内容变更通知

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

这些 API 现在要求在所有 URI 中为授权方定义有效的 ContentProvider。使用相关权限定义一个有效的 ContentProvider 可帮助您的应用防范来自恶意应用的内容变更,并防止将可能的私密数据泄露给恶意应用。

视图焦点

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

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

Android 8.0(API 级别 26)及更高版本包含 build 标识符字符串 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. 在界面 activity 中处理媒体按钮的方式没有变化:前台 activity 在处理媒体按钮事件时仍然优先。
    2. 如果前台 activity 不处理媒体按钮事件,系统会将该事件路由到最近在本地播放音频的应用。在确定哪些应用接收媒体按钮事件时,系统不会考虑媒体会话的活动状态、标志和播放状态。
    3. 如果应用的媒体会话已释放,系统会将媒体按钮事件发送到应用的 MediaButtonReceiver(如果有)。
    4. 对于任何其他情况,系统都会舍弃媒体按钮事件。

原生库

在针对 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)之前的平台版本中,如果通过调用 List.sort() 进行排序,则当迭代处理 ArrayList 以及在迭代过程中调用 sort() 时,会引发 ConcurrentModificationExceptionCollections.sort() 不会抛出异常。

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

类加载行为

Android 8.0(API 级别 26)进行了检查,以确保类加载器在加载新类时不会破坏运行时的假设。无论该类是从 Java(来自 forName())、Dalvik 字节码还是 JNI 中引用,都会执行这些检查。平台不会拦截 Java 对 loadClass() 方法的直接调用,也不会检查此类调用的结果。此行为不应影响运行良好的类加载器的正常运行。

平台将检查类加载器返回的类描述符是否与预期的描述符一致。如果返回的描述符与预期不符,平台会引发 NoClassDefFoundError 错误,并在异常日志中存储一条注明不一致之处的详细错误消息。

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

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

Android 8.0(API 级别 26)不支持多个类加载器同时尝试使用相同的 DexFile 对象来定义类。尝试进行此操作,会导致 Android 运行时引发 InternalError 错误,同时显示消息“Attempt to register dex file <filename> with multiple class loaders”。

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

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

在 Android 中,所有类加载器都被视为支持并行运行。当多个线程争用同一个类加载器加载相同的类时,第一个完成此操作的线程胜出,而操作结果将用于其他线程。无论类加载器是返回同一个类、返回不同的类还是引发异常,都将发生此行为。该平台会静默忽略此类异常。

注意 :在低于 Android 8.0(API 级别 26)的平台版本中,违反这些假设条件可能导致多次定义同一个类、由于类混淆造成堆损坏和其他不良影响。