Android 8.0(API 级别 26)除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更。本文重点介绍您应该了解并在开发应用时加以考虑的一些重要变更。
其中大多数变更都会影响所有应用,无论它们以哪个 Android 版本为目标平台。不过,有几项变更仅影响以 Android 8.0 为目标平台的应用。为清楚起见,本页面分为两部分:针对所有应用的变更和针对以 Android 8.0 为目标平台的应用所做的变更。
针对所有应用的变更
后台执行限制
作为 Android 8.0(API 级别 26)为延长电池续航时间引入的一项变更,当应用进入已缓存状态且没有活动组件时,系统会释放应用持有的所有唤醒锁。
此外,为了提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:
- 在后台运行的应用现在对后台服务的访问自由程度受到了限制。
- 应用无法使用其清单来注册大多数隐式广播(即,未专门针对应用的广播)。
默认情况下,这些限制仅适用于针对 O 的应用。不过,用户可以通过设置屏幕为任何应用启用这些限制,即使应用并未以 O 为目标平台。
Android 8.0(API 级别 26)还对特定方法进行了以下更改:
- 现在,如果某个以 Android 8.0 为目标平台的应用在不允许创建后台服务的情况下尝试使用该方法,则
startService()
方法会抛出IllegalStateException
。 - 新的
Context.startForegroundService()
方法会启动一个前台服务。即使应用在后台运行,系统也允许其调用Context.startForegroundService()
。不过,应用必须在创建服务后的 5 秒内调用该服务的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
-
getSaveFormData()
方法现在会返回false
。之前,此方法会返回true
。- 调用
setSaveFormData()
不再有任何效果。
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 行中。
- 系统不再将隧道连接请求中的 user-agent 和 proxy-authorization 标头发送到代理服务器。
在设置隧道时,系统不再将隧道 Http(s)网址Connection 中的 Proxy-authorization 标头发送到代理。相反,系统会生成 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。
- 某些屏蔽端口 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 首选项菜单。
- 在兼容设备上,当附近有已保存的高品质网络时,自动激活 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)引入了以下与安装来自未知来源的未知应用相关的更改:
- 现在,旧版设置
INSTALL_NON_MARKET_APPS
的值始终为 1。如需确定未知来源是否可以使用软件包安装程序安装应用,您应改用canRequestPackageInstalls()
的返回值。 - 如果您尝试使用
setSecureSetting()
更改INSTALL_NON_MARKET_APPS
的值,系统会抛出UnsupportedOperationException
。如需阻止用户安装使用未知来源的未知应用,您应改为应用DISALLOW_INSTALL_UNKNOWN_SOURCES
用户限制。 -
在搭载 Android 8.0(API 级别 26)的设备上创建的受管理资料会自动启用
DISALLOW_INSTALL_UNKNOWN_SOURCES
用户限制。对于升级到 Android 8.0 的设备上的现有受管理资料,DISALLOW_INSTALL_UNKNOWN_SOURCES
用户限制会自动启用,除非资料所有者(在升级之前)通过将INSTALL_NON_MARKET_APPS
设置为 1 来明确停用此限制。
如需详细了解如何安装未知应用,请参阅未知应用安装权限指南。
如需了解有关提高应用安全性的其他准则,请参阅面向 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 是可由用户重置的唯一 ID,用于投放广告。由 Google Play 服务提供。
其他设备制造商应继续提供
ANDROID_ID
。
-
对于在 OTA 之前安装到 Android 8.0(API 级别 26)(API 级别 26)版本的应用,除非卸载并在 OTA 后重新安装,否则
- 查询
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.dns1
、net.dns2
、net.dns3
和net.dns4
不再可用,这一变更可以加强平台上的隐私保护。 -
如需获取 DNS 服务器等网络信息,具有
ACCESS_NETWORK_STATE
权限的应用可以注册NetworkRequest
或NetworkCallback
对象。这些类在 Android 5.0(API 级别 21)及更高版本中提供。 -
废弃了 Build.SERIAL。需要知道硬件序列号的应用应改用新的
Build.getSerial()
方法,该方法需要READ_PHONE_STATE
权限。 -
LauncherApps
API 不再允许工作资料应用获取有关主资料的信息。当用户位于工作资料中时,LauncherApps
API 的行为就像同一资料组内的其他资料中未安装任何应用一样。与之前一样,尝试访问不相关的个人资料会导致 SecurityException。
权限
在 Android 8.0(API 级别 26)之前,如果应用在运行时请求权限并且被授予该权限,系统还会错误地向应用授予属于同一权限组且已在清单中注册的其他权限。
对于以 Android 8.0 为目标平台的应用,此行为已被纠正。系统只会向应用授予其明确请求的权限。但是,一旦用户向应用授予某项权限,后续对该权限组中的权限的所有后续请求都将自动授予。
例如,假设某个应用在其清单中同时列出了 READ_EXTERNAL_STORAGE
和 WRITE_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)中,媒体按钮事件的处理有所不同:
- 界面 activity 中媒体按钮的处理未发生变化:前台 activity 在处理媒体按钮事件时仍然优先。
- 如果前台 activity 不处理媒体按钮事件,系统会将事件路由到最近在本地播放音频的应用。在确定哪个应用接收媒体按钮事件时,不考虑媒体会话的活跃状态、标志和播放状态。
- 如果应用的媒体会话已释放,系统会将媒体按钮事件发送到应用的
MediaButtonReceiver
(如果有)。 - 对于任何其他情况,系统都会舍弃媒体按钮事件。
原生库
在以 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()
会抛出ConcurrentModificationException
。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
错误,并显示消息“Try to register dex file <filename>
with multiple class loaders”。
DexFile API 现已废弃,强烈建议您改用其中一个平台类加载器,包括 PathClassLoader
或 BaseDexClassLoader
。
注意 :您可以创建多个类加载器,用于引用文件系统中的同一个 APK 或 JAR 文件容器。这样做通常不会产生太多内存开销:如果存储而非压缩容器中的 DEX 文件,平台可以对它们执行 mmap
操作,而不是直接提取它们。但是,如果平台必须从容器中提取 DEX 文件,以这种方式引用 DEX 文件可能会消耗大量的内存。
在 Android 中,所有类加载器都被视为支持并行运行。当多个线程都争用同一个类加载器加载同一类时,第一个完成此操作的线程胜出,并将结果用于其他线程。无论类加载器是返回同一类、返回不同类还是抛出异常,都会发生此行为。平台会以静默方式忽略此类异常。
注意 :在低于 Android 8.0(API 级别 26)的平台版本中,破坏这些假设可能会导致多次定义同一类、因类混淆而导致的堆损坏和其他不良影响。