这些行为变更专门应用于针对 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 ( ) 实现,但具有以下限制:
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 ( ) 。
此项变更使平台行为更加一致:现在,两种方法都会引发 ConcurrentModificationException 。
媒体
框架会执行音频闪避。进行 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 时,应用不会失去焦点。新的 API 适用于需要暂停而不是闪避的应用。请注意,此行为无法在 Android O Developer Preview 1 版本中实现。
当用户打电话时,活动的媒体流将在通话期间静音。
所有与音频相关的 API 都应使用 AudioAttributes 而不是音频流类型来说明音频播放用例。仅为音量控制继续使用音频流类型。流类型(例如,已弃用的 AudioTrack constructor)的其他用途仍然有效,但是系统会将其记录为错误。
使用 AudioTrack 时,如果应用请求了足够大的音频缓冲区,则框架将尝试使用深度缓冲区输出(如果可用)。
在 Android O 中,媒体按钮事件的处理有所不同:
在界面操作组件中处理媒体按钮未发生变化:前台操作组件在处理媒体按钮时仍然优先。
如果前台操作组件不处理媒体按钮,系统会将媒体按钮路由到最近在本地播放音频的应用。在确定哪些应用接收媒体按钮事件时,不再考虑活动状态、标志和媒体会话的播放状态。即使在应用调用 setActive( false ) 后,媒体会话仍然可以接收媒体按钮事件。
如果应用的媒体会话已经释放,系统会将媒体按钮事件发送到应用的 MediaButtonReceiver(如果有)。
对于任何其他情况,系统都会舍弃媒体按钮事件。与其开始播放错误的应用,不如不播放任何东西。
下图汇总了新的媒体按钮路由逻辑:
类加载行为
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 with multiple class loaders” 。
DexFile API 现已弃用,强烈建议您改为使用此平台的类加载器之一,包括 PathClassLoader 或 BaseDexClassLoader。
注: 您可以创建多个引用文件系统中同一个 APK 或 JAR 文件容器的类加载器。这样做通常不会占用大量内存:如果存储而不压缩容器中的 DEX 文件,平台可以对此类文件执行 mmap 操作,而不直接提取它们。但是,如果平台必须从容器中提取 DEX 文件,以这种方式引用 DEX 文件可能占用大量内存。
在 Android 中,所有类加载器都被视为支持并行运行。当多个线程争用同一个类加载器加载相同的类时,第一个完成此操作的线程胜出,而操作结果将用于其他线程。无论类加载器是返回同一个类、返回不同的类还是引发异常,都将发生此行为。该平台静默忽略此类异常。
注意: 在低于 Android O 的平台版本中,违反这些假设条件可能导致多次定义同一个类、由于类混淆造成堆损坏和其他不良影响。