Android 8.0 系统和API的变化

最近在需求列表里看见一个“适配安卓8.0”的需求。一脸懵逼,之前没有做过根据新系统适配的工作。想想还是很有必要的,虽然现在8.0的设备不多,但是总有一天会占主流,不如提前过一遍,要不以后都是坑。


Android 8.0发布之后,官方遍发布了一篇名为Android 8.0 Behavior Changes的文章,列举了Android 8.0中系统和API行为的变化。其中又分为只对targetApi=26的应用有影响的变化和对所有应用都有影响的变化(无论targetApi是多少)。详细的过了一遍,有的内容实在是不熟悉,应用中也没用到,所以改之前都不知道什么样的。。。写的不对的地方欢迎指正

对所有应用都有影响的变化

这些变化会影响所有运行在Android 8.0系统的应用,无论他们的target api是多少。

后台执行限制

作为Android 8.0提高电池续航的手段,当你的应用没有任何活跃的组件而进入缓存状态(cached state),系统将释放所有应用持有的wake lock。

进一步,为了提高设备性能,系统会限制不在前台运行的应用的某些特定行为:

  • 后台应用访问后台服务自由度降低
  • 应用不能用manifests注册隐式广播
这些限定默认只对targetApi=26的应用起作用。但是对于targetApi不是O的应用,用户可以在“设置”中打开这些限制。

对于一些特定的方法,Android 8.0还有以下变化:

  • targetApi=26的应用,在没有创建后台服务的权限时调用startService()会抛出异常IllegalStateException
  • 新方法Context.startForegroundService()会开启一个前台服务。后台应用也可以调用这个方法,但是在服务创建之后5秒钟之内必须调用startForeground()

更多Background Execution Limits

安卓后台位置限制

为了电池续航、用户体验和系统健康,后台程序收到位置更新的频率减少。这个改变影响所有接收位置更新的应用,包括Google Play services。

受影响API:

  • Fused Location Provider (FLP)
  • Geofencing
  • GNSS Measurements
  • Location Manager
  • Wi-Fi Manager

更多Background Location Limits

App shortcuts

更多App Shortcuts

i18n

  • 一些Locale.getDefault()替换为Locale.getDefault(Category.DISPLAY)
  • 时区名解析更正确(使用SimpleDateFormat结果可能与之前版本不一样)
  • 更多

警告窗

如果一个应用使用权限SYSTEM_ALERT_WINDOW和以下任意一个窗口类型(window type)来显示窗口:

  • TYPE_PHONE
  • TYPE_PRIORITY_PHONE
  • TYPE_SYSTEM_ALERT
  • TYPE_SYSTEM_OVERLAY
  • TYPE_SYSTEM_ERROR

那么这些窗口会显示在使用了TYPE_APPLICATION_OVERLAY窗口类型的窗口之下。如果应用的targetApi=26,应用应该使用TYPE_APPLICATION_OVERLAY来显示警告窗。

以上列举的5种窗口类型,在api 26中被废弃了,非系统应用使用窗口应该使用 SYSTEM_ALERT_WINDOW

TYPE_APPLICATION_OVERLAY

需要申请SYSTEM_ALERT_WINDOW权限。会显示在所有活动窗口(activity windows)之上,但是会在重要的系统窗口(如状态栏、IME)之下。系统可以调整窗口的位置、大小和可见性,也可以随时调整进程的重要性防止低内存情况下窗口被杀。

输入和导航

随着Chrome OS应用的出现和一些在表格中的使用场景,键盘导航操作再次多了起来,在安卓8.0中,安卓升级了对键盘导航输入的支持。具体来说,安卓对元素的焦点行为做出了如下改变:

  • 如果控件没有指定焦点状态颜色,系统将根据活动的主题自动设置一个默认颜色。如果不希望控件有焦点颜色,那么可以把android:defaultFocusHighlightEnabled设置为false,或者在代码中setDefaultFocusHighlightEnabled() 传入false。
  • 启用Drawing > Show layout bounds来测试键盘输入多获取焦点的影响。在安卓8.0中打开这个选项后,拥有焦点的控件上回有个“X”。

toolbar自动作为一个键盘导航群(keyboard navigation clusters)

网页表格自动填充

由于安卓自动填充框架(Autofill Framework)内置了对自动填充的支持,所以对安装在安卓8.0系统的应用,对下列网页表格填充相关方法做了修改(大约相当于废弃了):

WebSettings

  • getSaveFormData() 现在返回false。之前在WebView存储了表格数据时会返回true
  • 调用setSaveFormData() 没有任何效果

WebViewDatabase

  • 调用clearFormData()没有任何效果
  • hasFormData()返回false。之前如果表格有数据则会返回true

辅助功能

更多Accessibility

网络和HTTP(S)链接

  • 没有body的OPTIONS请求,会有一个Content-Length: 0 的header,之前没有Content-Length的header
  • HttpURLConnection规范了含有空路径的URL,它会在host或authority后面加上/。例如,把http://example.com变成http://example.com/
  • proxy selector有关......
  • URI不能包含空标签
  • 更多......

蓝牙

ScanRecord.getBytes() 可以提取的数据长度做了改变:

  • getBytes() 不知道接收到了多少数据,所以应用不要依赖返回字节的最大或最小值。但是它会计算结果数组的长度。
  • 兼容蓝牙5的设备可能返回大于之前最大长度数据(大于60字节)
  • 如果远程设备对扫描没有响应,仍然可能返回小于60字节的数据

无缝连接

WiFi性能的一些优化

安全性

  • 不再支持SSLv3
  • 当对一个服务器建立HTTPS连接时,如果服务器没有正确实现TSL协议版本,HttpsURLConnection不再回退到之前版本再次尝试
  • 安卓8.0对所有应用程序都使用了SECCOMP过滤器,限制了可以使用的syscall为bionic暴露的syscall。尽管由于后向兼容的原因,还有其他可以使用的syscall,但安卓强烈反对开发者使用这些syscall
  • Webview对象运行在多进程模式。为了提供更好的安全性,网页内容会在一个应用进程外的独立进程处理
  • APK不一定在以“-1”或者“-2”结尾的文件夹中。应用应该使用sourceDir获得路径,不应该直接使用文件夹的格式
  • Native libraries

安卓8.0对未知来源的未知程序的变化:

  • INSTALL_NON_MARKET_APPS总是为1(8.0之前版本里,1为允许安装,0为不允许安装)。 现在应该使用canRequestPackageInstalls()来判断未知来源的程序能不能安装。
  • 使用setSecureSetting()修改INSTALL_NON_MARKET_APPS的值会抛出UnsupportedOperationException异常。应该使用DISALLOW_INSTALL_UNKNOWN_SOURCES来防止用户安装位置来源的应用。
  • 运行安卓8.0系统的设备自动设置DISALLOW_INSTALL_UNKNOWN_SOURCES为true(之前默认值为false)。老版本升级到8.0时,DISALLOW_INSTALL_UNKNOWN_SOURCES自动设置为true,除非在升级之前用户手动设置INSTALL_NON_MARKET_APPS为1。

更多User opt-in for unknown apps and sources,Security for Android Developers。

隐私性

  • 标识符的变化

    • 对于升级到8.0之前安装的应用,ANDROID_ID会保持不变。如果卸载后重新安装的话,ANDROID_ID将会改变。如果想保存卸载之前的ANDROID_ID的值,可以使用Key/Value Backup将老值和新值关联起来。
    • 对于安装在8.0系统的应用来说,ANDROID_ID根据应用签名和用户的不同而不同。ANDROID_ID唯一决定于应用签名、用户和设备三者的组合。
    • 只要应用签名不变,卸载再安装应用不会改变ANDROID_ID
    • 有Google Play服务的设备可以使用Advertising ID,其他设备应该继续使用ANDROID_ID
  • 访问net.hostname会返回null

未捕获异常日志

如果应用的Thread.UncaughtExceptionHandler没有对接默认的 Thread.UncaughtExceptionHandler,当有未捕获异常抛出时,系统不会杀死应用。安卓8.0开始,系统会打印堆栈跟踪;之前版本中系统则不会打印。

我们建议自定义的Thread.UncaughtExceptionHandler总是要和缺省的Thread.UncaughtExceptionHandler关联,这样做了的应用不会受到影响。

findViewById()签名改变

所有的findViewById()返回值由View变为 T

  • 这个变化可能导致已有代码对返回值的歧异,例如以findViewById()的返回值作为someMethod(View)someMethod(TextView)的调用参数时
  • 在Java 8中,在返回值不受限的情况下需要明确的转换成View类型。例如assertNotNull(findViewById(...)).someViewMethod())
  • 覆盖findViewById()方法的地方需要更新返回值类型(例如Activity.findViewById()

Contacts Provider的使用变化

之前版本中,获取了READ_CONTACTS权限的应用可以读取联系人信息包括:邮箱、电话、联系次数、最后联系时间等。

安卓8.0之后,获取READ_CONTACTS权限的应用仍然可以读取这些信息,但是返回的数据由原来的精确值改为近似值。

这个变化会影响以下参数:

  • TIMES_CONTACTED
  • TIMES_USED
  • LAST_TIME_CONTACTED
  • LAST_TIME_USED

Collection handling

没懂

安卓企业版

更多Android in the Enterprise

以安卓8.0为目标版本的变化

以下改变只对安卓8.0及以上版本有影响。targetSdkVersion=26或更高的应用需要作出相应调整。

警告窗

这里

内容改变通知

安卓8.0改变了ContentResolver.notifyChange(Uri, ContentObserver)registerContentObserver(Uri, boolean, ContentObserver)的行为。

这些接口要求Uri中的authority背后真实定义了一个有效的ContentProvider。

控件的焦点

可点击的控件默认为可聚焦的,如果希望一个控件可点击但是不可聚焦,可以在资源文件中设置android:focusable=false或者在setFocusable()中传入false

安全性

  • 如果网络安全配置关闭纯文本通信,WebView无法通过HTTP访问网站。WebView必须通过HTTPS。
  • Allow unknown sources系统被移除了,Install unknown apps权限控制未知来源的位置应用。Unknown App Install Permissions

使用用户账户信息

在安卓8.0中,应用只能使用authenticator拥有的账户信息或者用户授权的账户信息。仅仅申请GET_ACCOUNTS权限不足以获得账户信息的授权,为了获得使用权限,需要调用AccountManager.newChooseAccountIntent() ,或者其他authenticator相关的方法。得到了权限之后,应用可以调用AccountManager.getAccounts()来获得账户信息。

安卓8.0废弃了LOGIN_ACCOUNTS_CHANGED_ACTION,应该应该使用addOnAccountsUpdatedListener()来获取运行时账户变化。

隐私性

  • 系统属性net.dns1net.dns2net.dns3net.dns4不再可用。
  • 需要获取像DNS服务器这样的网络信息,应用需要获得 ACCESS_NETWORK_STATE权限,可以通过注册NetworkRequest或者NetworkCallback来获取网络信息。安卓5.0以上可用。
  • Build.SERIAL废弃了。应用应该使用Build.getSerial()来获取硬件序列号。这个方法需要READ_PHONE_STATE权限。
  • LauncherApps API不再允许工作模式的应用获取获取主模式的信息。当用户在工作模式时,LauncherApps API会认为其他模式没有安装任何应用。像以前一样,访问不相关的模式下的信息会导致SecurityExceptions。

权限

安卓8.0之前,如果应用在运行时申请一个权限,并且用户授予了这个权限,那么系统会错误的将这个权限所属的权限组里的并且在manifest里注册过的权限都授予这个应用。

对于targetApi为8.0的应用,以上行为已经被修正了,应用将只被授予其申请的权限。但是,如果应用之后再申请同一权限组中的其他权限时,将自动被授予。

例如,应用在Manifest里同时注册了READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAG两个权限。在targetApi=25或之前的版本中,当应用请求READ_EXTERNAL_STORAGE权限并且用户授权了之后,系统会自动授予WRITE_EXTERNAL_STORAG权限,因为READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAG同属STORAGE权限组其都在manifest里注册了。但在安卓8.0系统中,只有READ_EXTERNAL_STORAGE会被授权,但是当应用再次申请WRITE_EXTERNAL_STORAG权限时,系统会不提示用户直接授权。

多媒体

  • 系统默认实现自动音频闪避功能。也就是说,当其他应用以AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK来请求焦点的时候,当前焦点应用可以降低音量,但是不会收到onAudioFocusChange()回调,且不会失去音频焦点。有些应用可能不想要这种行为,如果这些应用想要失焦且暂停音频播放,在新API中提供了这种选项,开发者可以覆盖这种行为。
  • 当用户接电话时,在电话持续的这段时间音频自动静音
  • 所有音频相关的API应该使用AudioAttributes来控制音频重放,而不应该使用音频流类型参数。仅在音量控制时使用音频流类型。其他使用音频流类型的地方也会正常工作,但是系统会在日志中记录这种情况为一个错误。
  • 当使用AudioTrack时,如果应用需要很大的音频缓冲,系统会尝试使用深度缓冲
  • 在安卓8.0中多媒体按钮事件处理的差异:

    1. 在UI活动中的处理没有变化:前台活动仍然有处理多媒体按钮事件的优先级
    2. 如果前台活动不处理多媒体按钮事件,系统则把事件转发给本地最近播放过音频的应用。在决定哪个应用会获得多媒体按钮事件时,多媒体回话的活跃状态、标志和重放状态并不会影响最终决策。
    3. 如果应用的多媒体会话已经被释放了,如果应用有MediaButtonReceiver的话,系统会把多媒体按钮事件传给应用的MediaButtonReceiver
    4. 处理上述情况之外,系统则舍弃多媒体按钮事件

本地库

以安卓8.0为目标的应用中,如果本地库中含有同时可写和可执行的装入段,则本地库不会被加载。因为这个改变有些应该可能不能正常工作了。这个改变是一个加强安全的措施。更多Writable and Executable Segments。

链接器改变随着目标api的改变而改变。如果在某一api版本中链接器有变化,那么应用则不能正常工作。如果目标api设为链接器变化之前的api版本,日志中会出现一个警告。

集合处理

在安卓8.0中Collections.sort()的实现依赖于List.sort()的实现。在安卓7.x(api 24和25)中正相反,默认的List.sort()实现依赖于Collections.sort()的实现。

这个变化使Collections.sort()可以受益于优化后的List.sort(),但是有如下限制:

  • 实现List.sort()时不要调用Collections.sort(),否则会引起死循环,引起stakeoverflow。如果想要默认的List行为,尽量不要覆盖sort()方法。

    如果父类覆盖了sort()且实现的不太好,那么可以依赖List.toArray()Arrays.sort()ListIterator.set()自己实现sort()方法,例如:

    @Override
    public void sort(Comparator c) {
      Object[] elements = toArray();
      Arrays.sort(elements, c);
      ListIterator iterator = (ListIterator) listIterator();
      for (Object element : elements) {
        iterator.next();
        iterator.set((E) element);
      }
    } 

    大多数情况下,实现List.sort()时候也可以委托给其他API来实现,例如:

    @Override
    public void sort(Comparator comparator) {
      if (Build.VERSION.SDK_INT <= 25) {
        Collections.sort(this);
      } else {
        super.sort(comparator);
      }
    }

    如果使用后一种实现仅仅是因为想为所有API版本提供一个统一的sort()方法,那么应该取一个唯一的名字比如sortCompat(),而不是覆盖sort()

  • 在List实现中,有些地方调用了sort(),对于这些实现来说,Collections.sort()的变化会带来实现中结构上的修改。例如,在8.0之前的系统中,在遍历ArrayList的过程中调用sort(),如果这个sort()是通过List.sort()实现的话,则会引发ConcurrentModificationException异常,但是通过Collections.sort()则不会引发异常。

    这个变化使系统行为更一致:两种做法都会引发异常。

  • 类加载

    安卓8.0系统中在加载新类的时候,会做一些检查以确保类加载器不会破坏运行时原有的设定。不论新类是从Java引用的,还是从Dalvik字节码引用的,还是JNI,这些检查都会执行。系统不会拦截在Java中调用的loadClass()方法及其返回值。这个新行为对于正常的类加载器没有影响。

    系统会检查类加载器返回的类描述符是否匹配期望的类描述符,如果描述符不匹配,系统会抛出NoClassDefFoundError错误,并且会在异常中记录详细信息。

    系统还会检查描述符是否有效。这个检查会捕获由于JNI调用间接加载类的方法(如GetFieldID())中无效的描述符引起的错误。例如,签名中java/lang/String的字段是无法找到,因为这不是一个有效的签名。正确的签名应该为Ljava/lang/String;

    和JNI调用FindClass()不同,这里java/lang/String是有效的。

    安卓8.0不知道多个类加载器使用同一个DexFile对象定义类。这样做会引起InternalError错误,错误信息为“Attempt to register dex file with multiple class loaders”。

    DexFile API已经被废弃了,强烈建议开发者使用包括PathClassLoaderBaseDexClassLoader在内的系统类加载器。

    注意:可以创建多个类加载器引用文件系统中的同一个APK或者JAR文件容器。这么做正常来说不会消耗更多的内存:如果DEX文件是压缩文件,系统会调用 mmap,而不会解压它。但是,如果系统必须解压DEX文件,那么这样做是很耗内存的。

    在安卓中,类加载器都是有并行能力的。当多线程用同一个类加载器加载同一个类的时候,最先完成的线程获胜,其他线程使用其返回结果。这种行为和类加载器的返回值无关。无论类加载器返回的是与加载的类相同的类还是不同的类,或者是返回异常,对这种多线程情况的处理都是一样的。

    注意:在安卓8.0以前,不遵循这些设定的话,会导致多次定义同一个类、由于类冲突导致的堆内存损坏或者其他不良后果。

    你可能感兴趣的:(文档,android,总结)