Android 8.0 (API 26) 适配
针对所有API级别的应用
后台执行限制
- 为了提供续航时间,对于收入后台(进入已缓存状态)、没有活动组件的App,系统会解除App所具有的所有唤醒锁
- 后台运行的应用对后台服务的访问受限
- 应用无法使用清单文件注册大部分隐式广播
默认只针对target 8.0的App,但是用户可以在Settings中手动启动这些限制,针对所有API级别的App
- 对特定API做了限制
- target 8.0的App在不允许创建后台服务的情况下调用startService()时,会引发IllegalStateException
- Context.startForegroundService()函数允许在后台运行的时候被调用,但是创建服务后要在5秒内调用该服务的startForeground()
详情参阅后台执行限制
后台位置限制
为节约电量和用户体验,限制后台应用接受位置更新的频率,受影响的API:
- Fused Location Provider (FLP)
- Geofencing
- GNSS Measurements
- Location Manager
- Wi-Fi Manager
详情参阅后台位置限制
应用快捷键
- com.android.launcher.action.INSTALL_SHORTCUT变为隐式广播,替代方案ShortcutManager
- ACTION_CREATE_SHORTCUT Intent功能增强
详情参见固定快捷方式和微件预览功能指南
语言区域和国际化
调用
Currency.getDisplayName(null)会引发NullPointerException,以与文档规定的行为保持一致。时区名称的分析方法发生变化。之前,Android 设备使用在启动时取样的系统时钟值,缓存用于分析日期时间的时区名称。因此,如果在启动时或其他较为罕见的情况下系统时钟出错,可能对分析产生负面影响。
现在,一般情况下,在分析时区名称时分析逻辑将使用 ICU 和当前系统时钟值。此项变更可提供更加准确的结果,如果您的应用使用
SimpleDateFormat等类,此结果可能与之前的 Android 版本不同。Android 8.0 将 ICU 的版本更新至版本 58。
Alert window
如果App使用SYSTEM_ALERT_WINDOW权限并且尝试下面某种窗口类型来显示Alert Window:
- TYPE_PHONE
- TYPE_PRIORITY_PHONE
- TYPE_SYSTEM_ALERT
- TYPE_SYSTEM_OVERLAY
- TYPE_SYSTEM_ERROR
- Target < 8.0,Alert始终显示在TYPE_APPLICATION_OVERLAY类型窗口的下方;
- Target >= 8.0,App会使用TYPE_APPLICATION_OVERLAY类型显示Alert
输入和导航
详情参阅支持键盘导航
网页表单自动填充
WebSettings
getSaveFormData()函数现在返回false。之前,此函数返回true。- 调用
setSaveFormData()不再有任何效果。WebViewDatabase
- 调用
clearFormData()不再有任何效果。hasFormData()函数现在返回false。之前,当表单包含数据时,此函数返回true。
无障碍功能
无障碍服务现在可以识别TextView对象内的所有ClickableSpan实例。
详情参阅无障碍功能
网络连接和HTTPS
Android 8.0 对网络连接和 HTTP(S) 连接行为做出了以下变更:
无正文的 OPTIONS 请求具有
Content-Length: 0标头。之前,这些请求没有Content-Length标头。HttpURLConnection 在包含斜线的主机或颁发机构名称后面附加一条斜线,使包含空路径的网址规范化。例如,它将
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 在实现 HttpsURLConnection 时不会执行不安全的 TLS/SSL 协议版本回退。
对隧道 HTTP(S) 连接处理进行了如下变更:
在通过连接建立隧道 HTTP(S) 连接时,系统会在 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 之前的版本,即使 send() 调用成功,后续的 recv() 调用也会引发 SocketException。为确保一致性,现在这两个调用均会引发 SocketException。
在回退到 TCP Echo 协议之前,InetAddress.isReachable() 会尝试执行 ICMP。
- 对于某些屏蔽端口 7 (TCP Echo) 的主机(例如 google.com),如果它们接受 ICMP Echo 协议,现在也许能够访问它们。
对于确实无法访问的主机,此项变更意味着调用需要两倍的时间才能返回结果。
蓝牙
Android 8.0 对
ScanRecord.getBytes()函数检索的数据长度做出以下变更:
getBytes()函数对于所接收的字节数不作任何假定。因此,应用不应受所返回的任何最小或最大字节数的影响。相反,应用应当计算所返回数组的长度。- 兼容蓝牙 5 的设备返回的数据长度可能会超出之前最大约 60 个字节的限制。
- 如果远程设备未提供扫描响应,则也可能返回少于 60 个字节的数据。
无缝连接
WLAN体验优化
安全性
Android 8.0 包含以下与安全性有关的变更:
- 此平台不再支持 SSLv3。
- 在与未正确实现 TLS 协议版本协商的服务器建立 HTTPS 连接时,
HttpsURLConnection不再尝试回退到之前的 TLS 协议版本并重试的权宜方法。- Android 8.0 将使用安全计算 (SECCOMP) 过滤器来过滤所有应用。允许的系统调用列表仅限于通过 bionic 公开的系统调用。此外,还提供了其他几个后向兼容的系统调用,但我们不建议使用这些系统调用。
- 现在,您的应用的
WebView对象将在多进程模式下运行。网页内容在独立的进程中处理,此进程与包含应用的进程相隔离,以提高安全性。- 您无法再假定 APK 驻留在名称以 -1 或 -2 结尾的目录中。应用应使用
sourceDir获取此目录,而不能直接使用目录格式。- 如需了解与使用原生库有关的安全性增强的信息,请参阅原生库。
有关提升应用安全性的其他准则,请参阅面向 Android 开发者的安全性。
隐私性
Android 8.0 对平台做出了以下与隐私性有关的变更。
- 现在,平台改变了标识符的处理方式。
- 对于在 OTA 之前安装到某个版本 Android 8.0(API 级别 26)的应用,除非在 OTA 后卸载并重新安装,否则
ANDROID_ID的值将保持不变。要在 OTA 后在卸载期间保留值,开发者可以使用密钥/值备份关联旧值和新值。- 对于安装在运行 Android 8.0 的设备上的应用,
ANDROID_ID的值现在将根据应用签署密钥和用户确定作用域。应用签署密钥、用户和设备的每个组合都具有唯一的ANDROID_ID值。因此,在相同设备上运行但具有不同签署密钥的应用将不会再看到相同的 Android ID(即使对于同一用户来说,也是如此)。- 只要签署密钥相同(并且应用未在 OTA 之前安装到某个版本的 O),
ANDROID_ID的值在软件包卸载或重新安装时就不会发生变化。- 即使系统更新导致软件包签署密钥发生变化,
ANDROID_ID的值也不会变化。- 要借助一个简单的标准系统实现应用获利,请使用广告 ID。广告 ID 是 Google Play 服务针对广告服务提供的唯一 ID,此 ID 可由用户重置。
- 查询
net.hostname系统属性返回的结果为空。
记录未捕获的异常
如果某个应用安装的
Thread.UncaughtExceptionHandler未移交给默认的Thread.UncaughtExceptionHandler,则当出现未捕获的异常时,系统不会终止应用。从 Android 8.0 开始,在此情况下系统将记录异常堆栈跟踪情况;在之前的平台版本中,系统不会记录异常堆栈跟踪情况。我们建议,自定义
Thread.UncaughtExceptionHandler实现始终移交给默认处理程序处理。
联系人Provider关于使用情况统计变更
从Android 8.0开始,具有READ_CONTACTS权限的应用任然可以读取每个联系人的使用情况数据,但是获取的都是近似值而不是精确值(系统内部保留精确值),受影响的查询类型:
- TIMES_CONTACTED
- TIMES_USED
- LAST_TIME_CONTACTED
- LAST_TIME_USED
集合
现在,
AbstractCollection.removeAll()和AbstractCollection.retainAll()始终引发NullPointerException;之前,当集合为空时不会引发NullPointerException。
Android企业版
详情参阅企业中的 Android
针对Android 8.0的应用
这些行为变更专门应用于 Target >= 8.0 的应用,开发者必须修改其应用以正确支持这些行为(如果适用)。
Alert window
- 使用SYSTEM_ALERT_WINDOW的时候必须使用新窗口类型TYPE_APPLICATION_OVERLAY;
- 新窗口类型特性:
- 该类型窗口始终显示在状态栏和输入法等关键系统窗口下面;
- 系统可以移动这种类型的窗口或调整大小(改善显示效果)
- 用户可以通过设置页面组织应用显示该类型提醒窗口
Content change notifications
Android 8.0 更改了
ContentResolver.notifyChange()和registerContentObserver(Uri, boolean, ContentObserver)在针对 Android 8.0 的应用中的行为方式。现在,这些 API 需要在所有 URI 中为颁发机构定义一个有效的
ContentProvider。使用相关权限定义一个有效的ContentProvider可帮助您的应用防范来自恶意应用的内容变更,并防止将可能的私密数据泄露给恶意应用。
View focus
可点击的View现在默认也可以获取focus,可以通过布局XML或代码置为false
安全性
如果您的应用的网络安全性配置选择退出对明文流量的支持,那么您的应用的
WebView对象无法通过 HTTP 访问网站。每个WebView对象必须转而使用 HTTPS。有关提升应用安全性的其他准则,请参阅面向 Android 开发者的安全性。
账号访问和可检测性
详情参阅帐号访问和可检测性
隐私性
- 系统属性
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权限。 -
LauncherAppsAPI 不再允许工作资料应用获取有关主个人资料的信息。当某个用户在托管配置文件中时,LauncherAppsAPI 的行为就像同一配置文件组的其他配置文件中未安装任何应用一样。和之前一样,尝试访问无关联的个人资料会引发 SecurityExceptions。
权限
在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
例如,假设某个应用在其清单中列出
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE。应用请求READ_EXTERNAL_STORAGE,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予WRITE_EXTERNAL_STORAGE,因为该权限也属于同一STORAGE权限组并且也在清单中注册过。如果该应用针对的是 Android 8.0,则系统此时仅会授予READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。
媒体
详情参阅媒体
原生库
在针对 Android 8.0 的应用中,如果原生库包含任何可写且可执行的加载代码段,则不会再加载原生库。倘若某些应用的原生库包含不正确的加载代码段,则此变更可能会导致这些应用停止工作。这是一种安全加强措施。
如需了解详细信息,请参阅可写且可执行的代码段。
与早期的开发者预览版相同,Android 8.0 还有助于更轻松地发现所有与链接器有关的问题。链接器的变更绑定到应用的目标 API 级别。如果应用的目标 API 级别发生链接器变更,则该应用无法加载该库。如果您的目标 API 级别低于发生链接器变更的 API 级别,则 logcat 会显示一条警告消息。在预览版期间,与链接器有关的问题不仅会显示在 logcat 中,也会以 toast 的形式显示。对于特定的 API 级别,警告可能会变成错误,此变更有助于提前发现此类问题。
集合
在 Android 8.0 中,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 之前的平台版本中,如果通过调用List.sort()进行排序,则当迭代处理ArrayList以及在迭代过程中调用sort()时,会引发ConcurrentModificationException。而Collections.sort()则不会引发异常。此项变更使平台行为更加一致:现在,两种方法都会引发
ConcurrentModificationException。
Class-loading
Android 8.0 检查确保类加载器在加载新类时不会违反运行时假设条件。不论类引用自 Java(来自
forName())、Dalvik 字节码还是 JNI,都会执行这些检查。平台不会拦截 Java 对loadClass()函数的直接调用,也不会检查此类调用的结果。此行为不应影响运行良好的类加载器的正常运行。平台将检查类加载器返回的类描述符是否与预期的描述符一致。如果返回的描述符与预期不符,平台会引发
NoClassDefFoundError错误,并在异常日志中存储一条注明不一致之处的详细错误消息。平台还检查请求的类描述符是否有效。此检查捕获间接加载诸如
GetFieldID()等类的 JNI 调用,向这些类传递无效的描述符。例如,找不到包含java/lang/String签名的字段,是因为此签名无效;它应为Ljava/lang/String;。这与 JNI 对
FindClass()的调用不同,其中java/lang/String是一个有效的完全限定名称。Android 8.0 不支持多个类加载器同时尝试使用相同的 DexFile 对象来定义类。尝试进行此操作,会导致 Android 运行时引发
InternalError错误,同时显示消息“Attempt to register dex file<filename>with multiple class loaders”。DexFile API 现已弃用,强烈建议您改为使用此平台的类加载器之一,包括
PathClassLoader或BaseDexClassLoader。注: 您可以创建多个引用文件系统中同一个 APK 或 JAR 文件容器的类加载器。这样做通常不会占用大量内存:如果存储而不压缩容器中的 DEX 文件,平台可以对此类文件执行
mmap操作,而不直接提取它们。但是,如果平台必须从容器中提取 DEX 文件,以这种方式引用 DEX 文件可能占用大量内存。在 Android 中,所有类加载器都被视为支持并行运行。当多个线程争用同一个类加载器加载相同的类时,第一个完成此操作的线程胜出,而操作结果将用于其他线程。无论类加载器是返回同一个类、返回不同的类还是引发异常,都将发生此行为。该平台静默忽略此类异常。
注: 在低于 Android 8.0 的平台版本中,违反这些假设条件可能导致多次定义同一个类、由于类混淆造成堆损坏和其他不良影响。