Android O 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更。本文重点介绍您应该了解并在开发应用时加以考虑的一些主要变更。
其中大部分变更会影响所有应用,而不论应用针对的是何种版本的 Android。不过,有几项变更仅影响针对 Android O 的应用。为清楚起见,本页面分为两个部分:针对所有 API 级别的应用和针对 Android O 的应用。
针对所有 API 级别的应用
这些行为变更适用于在 Android O 平台上运行的所有应用,无论这些应用是针对哪个 API 级别构建。所有开发者都应查看这些变更,并修改其应用以正确支持这些变更(如果适用)。
网络连接和 HTTP(S) 连接
Android O 对网络连接和 HTTP(S) 连接行为做出了以下变更:
集合的处理
现在,AbstractCollection.removeAll ( ) 和 AbstractCollection.retainAll ( ) 始终引发 NullPointerException;之前,当集合为空时不会引发 NullPointerException。此项变更使行为符合文档要求。
记录未捕获的异常
如果某个应用安装的 Thread.UncaughtExceptionHandler 未移交给默认的 Thread.UncaughtExceptionHandler,则当出现未捕获的异常时,系统不会终止应用。从 Android O 开始,在此情况下系统将记录异常堆栈跟踪情况;在之前的平台版本中,系统不会记录异常堆栈跟踪情况。
我们建议,自定义 Thread.UncaughtExceptionHandler 实现始终移交给默认处理程序处理;遵循此建议的应用不受 Android O 此项变更的影响。
输入和导航
随着 Android 应用出现在 Chrome 操作系统和平板电脑等其他大尺寸设备上,我们看到,用户在 Android 应用中又重新开始使用键盘导航。在 Android O 中,我们又再次使用键盘作为导航输入设备,从而为基于箭头键和 Tab 键的导航构建了一种更可靠并且可预测的模型。
尤其要指出的是,我们对元素焦点行为做出以下变更:
另外,Android O 中的所有工具栏元素自动组成键盘导航键区,用户可以更加轻松地导航进入和离开每个作为一个整体的工具栏。
如需详细了解如何在您的应用中改善对键盘导航的支持,请阅读以下链接中的支持键盘导航指南。
(https://developer.android.google.cn/training/keyboard-input/navigation.html)
安全性
Android O 包含以下与安全性有关的变更:
有关提升应用安全性的其他准则,请参阅以下链接中的面向 Android 开发者的安全性。
(https://developer.android.google.cn/topic/security/index.html)
后台执行限制
Android O 为提高电池续航时间而引入的变更之一是,当您的应用进入已缓存状态时,如果没有活动的组件,系统将解除应用具有的所有唤醒锁。
此外,为提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:
Android O 还对特定函数做出了以下变更:
如需了解详细信息,请参阅以下链接中的后台执行限制。
(https://developer.android.google.cn/preview/features/background.html)
隐私性
Android O 对平台做出了以下与隐私性有关的变更:
针对 Android O 的应用
这些行为变更专门应用于针对 O 平台或更高平台版本的应用。针对 Android O 或更高平台版本进行编译,或将 targetSdkVersion 设为 Android O 或更高版本的应用开发者必须修改其应用以正确支持这些行为(如果适用)。
内容变更通知
Android O 更改了 ContentResolver.notifyChange ( ) 和 registerContentObserver ( Uri, boolean, ContentObserver ) 在针对 Android O 的应用中的行为方式。
现在,这些 API 需要在所有 URI 中为颁发机构定义一个有效的 ContentProvider。使用相关权限定义一个有效的 ContentProvider 可帮助您的应用防范来自恶意应用的内容变更,并防止将可能的私密数据泄露给恶意应用。
视图焦点
可点击的 View 对象现在默认也可以成为焦点。如果您希望 View 对象可点击但不可成为焦点,请在包含 View 的布局 XML 文件中将 android:focusable 属性设置为 false,或者将 false 传递至应用界面逻辑中的 setFocusable ( ) 。
权限
在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
对于针对 Android O 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
例如:
假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 。应用请求 READ_EXTERNAL_STORAGE ,并且用户授予了该权限。
如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE ,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。
如果该应用针对的是 Android O,则系统此时仅会授予 READ_EXTERNAL_STORAGE ;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE ,则系统会立即授予该权限,而不会提示用户。
集合的处理
在 Android O 中,Collections.sort ( ) 是在 List.sort ( ) 的基础上实现的。在 Android 7.x(API 级别 24 和 25)中,则恰恰相反。在过去,List.sort ( ) 的默认实现会调用 Collections.sort ( ) 。
此项变更使 Collections.sort ( ) 可以利用优化的 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 ( ) 。
此项变更使平台行为更加一致:现在,两种方法都会引发 ConcurrentModificationException 。
媒体
下图汇总了新的媒体按钮路由逻辑:
类加载行为
Android O 检查确保类加载器在加载新类时不会违反运行时假设条件。不论类引用自 Java(来自 forName ( ) )、Dalvik 字节码还是 JNI,都会执行这些检查。平台不会拦截 Java 对 loadClass ( ) 函数的直接调用,也不会检查此类调用的结果。此行为不应影响运行良好的类加载器的正常运行。
平台将检查类加载器返回的类描述符是否与预期的描述符一致。如果返回的描述符与预期不符,平台会引发 NoClassDefFoundError 错误,并在异常日志中存储一条注明不一致之处的详细错误消息。
平台还检查请求的类描述符是否有效。此检查捕获间接加载诸如 GetFieldID ( ) 等类的 JNI 调用,向这些类传递无效的描述符。例如,找不到包含 java/lang/String 签名的字段,是因为此签名无效;它应为 Ljava/lang/String; 。
这与 JNI 对 FindClass ( ) 的调用不同,其中 java/lang/String 是一个有效的完全限定名称。
Android O 不支持多个类加载器同时尝试使用相同的 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 O 的平台版本中,违反这些假设条件可能导致多次定义同一个类、由于类混淆造成堆损坏和其他不良影响。