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。
  • 测试应用是否在每个用例中都表现出预期的行为。
  • 请考虑使用一体化位置信息提供程序 (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 中的功能,但您必须在应用中手动将其转换为应用快捷方式。

如需详细了解对应用快捷方式的更改,请参阅固定快捷方式和 widget 功能指南。

语言区域和国际化

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 为目标平台的应用的行为变更内的提醒窗口的常见窗口类型部分。

输入和导航

随着 ChromeOS 上和其他大外形规格(如平板电脑)上的 Android 应用的出现,我们发现 Android 应用中的键盘导航功能重新开始复苏。在 Android 8.0(API 级别 26)中,我们重新解决了将键盘用作导航输入设备的问题,从而为基于箭头和 Tab 的导航打造了更可靠、可预测的模式。

具体而言,我们对元素焦点行为做出了以下更改:

  • 如果您尚未为 View 对象(前景或背景可绘制对象)定义任何焦点状态颜色,框架现在会为 View 设置默认的焦点突出显示颜色。此焦点突出显示标志是基于 activity 主题的涟漪可绘制对象。

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

  • 如需测试键盘输入对界面元素焦点有何影响,您可以启用绘图 > 显示布局边界开发者选项。在 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 has empty labels in the hostname. 此格式不正确,未来的 Android 版本中将不被接受。” Android 8.0 移除了此权宜解决方法;系统会针对格式错误的 URI 返回 null。

  • Android 8.0 的 Https网址Connection 实现不会执行不安全的 TLS/SSL 协议版本回退。
  • 对隧道 HTTP(S) 连接的处理方式发生了以下变更:
    • 通过连接建立 HTTPS 隧道时,系统在将此信息发送到中间服务器时,会将端口号 (:443) 正确放入主机行中。以前,端口号仅出现在 CONNECT 行中。
    • 系统不再将隧道式请求中的用户代理和代理授权标头发送到代理服务器。

      在设置隧道时,系统不再将隧道式 Http(s)网址Connection 中的 Proxy-authorization 标头发送到代理。相反,系统会生成代理授权标头,并在该代理响应初始请求发送 HTTP 407 时将该标头发送到该代理。

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

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

蓝牙

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 对象可在多进程模式下运行。Web 内容在单独的进程中处理,进程与包含应用的进程相隔离,以提高安全性。
  • 您无法再假定 APK 位于名称以 -1 或 -2 结尾的目录中。应用应使用 sourceDir 获取目录,而不能直接依赖目录格式。
  • 如需了解与使用原生库相关的安全增强功能,请参阅原生库

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

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

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

隐私权

Android 8.0(API 级别 26)对平台进行了以下与隐私保护相关的更改。

  • 现在,平台会以不同的方式处理标识符。
    • 对于在 OTA 之前安装到 Android 8.0(API 级别 26)(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 系统会在内部保留确切的值,因此这项变更不会影响自动填充 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 中的授权定义有效的 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()

  • 在调用 sort() 的 List 实现中,Collections.sort() 现在属于结构性修改。例如,在 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;

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

Android 8.0(API 级别 26)不支持多个类加载器尝试使用同一个 DexFile 对象定义类。尝试这样做会导致 Android 运行时抛出 InternalError 错误,并显示消息“Try to registration dex file <filename> with multiple class loaders”。

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

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

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

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