Android 版本适配6.0到8.0

    不知不觉,Android系统已经发展了10年之久,按照谷歌的命名风格,每个安卓版本都会以英文字母的顺序来进行命名,并且它们都有一个好吃的甜品代号,从最初的纸杯蛋糕(cupcake)到现在的奥利奥(Oreo)以及最新的beta版本的开心果冰淇淋(Pistachio Ice Cream),总共26个字母,现在只剩下10个字母了,十年后会有什么变化呢?有点小堪忧……

搞笑

    牛皮吹到这里,下面进入正题。既然Android版本的迭代如此之快,那么系统的适配工作也必须同步进行。系统适配对于项目的发展尤为重要,只有不断地提升性能与体验才能得到用户的认可。但即使这样,相信还是有一部分的app还没来得及做系统的适配工作,那么这次的适配内容就从2015年推出的的6.0的版本开始讲起,一共是三个大版本,6.0(Marshmallow)、7.0(Nougat)、8.0(Oreo),先看下这三个版本有哪些主要的变化:

Android6.0

Android 6.0

发布时间:2015年5月28日
主要变化:

1.运行时权限
2. 增加低电耗模式和应用待机模式
3. 取消支持 Apache HTTP 客户端
4. 移除硬件标识符访问权
5. WLAN 和网络连接变更
6. 相机服务变更

Android 7.0

Android 7.0

发布时间: 2016年8月22日
主要变化:

  1. 私有文件访问权限更改
  2. 多窗口支持(分屏显示)
  3. 通知增强功能
  4. 随时随地低电耗模式
  5. 多语言区域支持,更多语言
  6. 新增的表情符号
  7. Chrome 和 WebView 配合使用
  8. APK signature scheme v2

Android 8.0

这里写图片描述

发布时间:2017年8月22日
主要变化:

  1. 通知渠道
  2. 启动图标
  3. 统一的布局外边距和内边距
  4. 自动填充框架
  5. 画中画模式
  6. 多显示器支持
  7. 媒体增强功能

可以看到,每个版本都有比较多的变化,但并不是所有内容都需要适配,红色标注就是最需要进行系统适配的内容,也是文章所讲的内容。如果已经准备好去适配某个版本,那么请将targetSdkVersion改为对应的版本号,点击sync Now,开启填坑之路吧。


6.0—运行时权限

    在6.0之前的版本中,我们在使用某项权限时,只需要在Manifest中声明就可以使用了,但是在6.0之后,某些权限(例如:通讯录、位置、相机)不仅需要在Manifest中声明,还要在app运行的时候动态地申请并被用户允许才能正常使用,这类权限称为危险权限(Dangerous Permission)。与其对应的是正常权限(Normal Permissions),正常权限(例如:蓝牙,网络)只需要在清单文件声明即可。具体的权限列表可以参考谷歌的官方文档

适配流程:

适配过程相对比较简单,下面以相机权限CAMERA 为例子说明,分为以下几个步骤:
1.在Manifest中声明所需权限:

<uses-permission android:name="android.permission.CAMERA"/>

2.调用ContextCompat.checkSelfPermission()检查权限:

if(ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)
               != PackageManager.PERMISSION_GRANTED) {
                //权限未授予,调用requestPermission()申请权限
 }else{
                 //权限已授予
 }

checkSelfPermission()方法接受两个参数。第一个参数为Context对象,第二个参数为需要进行检查的权限,类型为String。返回值是一个int常量,返回PackageManager.PERMISSION_GRANTED表示权限已经被授予,返回PackageManager.PERMISSION_DENIED表示权限未被授予。

3.如果权限未被授予,调用ActivityCompat.requestPermissions()申请权限:

 ActivityCompat.requestPermissions(this,
                   new String[]{Manifest.permission.CAMERA}, MY_REQUEST_CODE);

requestPermissions()接受三个参数,第一个参数是Context对象,第二个参数是一个String数组,可以同时申请多个权限,第三个参数是请求码。

4.重写Activity的onRequestPermissionsResult()处理申请回调:

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_REQUEST_CODE: {

            if (grantResults.length > 0 &&
            grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            //用户允许该权限

            } else {

            //用户拒绝该权限

            }
            return;
        }
    }
}

其中requestCode就是请求码,在这里就是上面的MY_REQUEST_CODE,permissions就是申请的权限,grantResults是请求的结果,数组大小与permissions对应。由于我们只请求了一个CAMERA权限,所以只需要看grantResults的第一个值就好了,根据结果做出相应的操作。至此,运行时权限申请就完成了。整体看起来,流程比较清晰和简单,就是有点繁琐(每个权限都要这么写的话),那这里推荐一个比较好用的封装库RxPermission


7.0—多窗口模式(分屏模式)

    多窗口模式是7.0版本的一个新特性,顾名思义,也就是让一个屏幕分成多个窗口,可以在一个屏幕上显示多个应用,多窗口模式共分为三种(分屏模式、画中画、自由形状),在手机上比较多用的是分屏模式,如下图所示(个人感觉:在屏幕不是特别大的手机上效果一般)。画中画模式在Android TV上比较适用,自由模式在TNT上比较适用…..这里不展开讨论。提供一个官方文档的链接。

如何进入分屏模式?每个厂家的设置都可能不一样,有的长按菜单键,有的长按返回键,具体可以百度下。

多窗口模式

分屏模式具有以下几个特点:

  1. 分屏模式不会更改 Activity 生命周期
  2. 在分屏模式模式下,只有一个Activity会获得焦点,其他Activity处于Paused状态
  3. 在分屏模式下调整窗口大小会回调onConfigurationChanged
  4. 如果根Activity允许多窗口模式,那么与它在同一个栈种的Activity都被允许多窗口模式(即使没有设置)。
  5. 某些系统 UI 自定义选项将被禁用;例如,在非全屏模式中,应用无法隐藏状态栏。
  6. 系统将忽略对android:screenOrientation 属性所作的更改。

适配流程:

1.如果布局外层是ScrollView、RecycleView、ListView的界面,那么在分屏模式下是比较正常,可以尝试让这些界面支持分屏模式。

2.如果是固定宽高的界面,设置android:minimalHeightandroid:minimalWidth来设置分屏模式下的最小宽高。

<activity android:name=".MyActivity">
    <layout 
          android:minimalHeight="450dp"
          android:minimalWidth="300dp" />
</activity>

(编译出错,问题未知)

3.如果不想适配分屏模式,可以在Manifest的activity标签下设置属性android:resizeableActivity="false"


7.0—访问应用私有文件

    在7.0版本之前,我们可以通过File://这一类Uri访问其他应用的私有文件或者让其他应用访问自己的私有文件。什么是私有文件?如果不太清楚这个概念的话,可以看这一篇博客,讲解的非常好,另外他还有一篇关于7.0的适配也讲解的非常详细。

http://yifeng.studio/2017/04/27/android-app-file-storage-directory/

从7.0版本开始,这么做就不行了,如果尝试传递 file:// Uri来访问其他应用的私有文件会触发 FileUriExposedException异常,分享私有文件内容的推荐方法是使用 FileProvider,FileProvider是v4包下的一个类,继承自ContentProvider。所以可以猜测通过FileProvider对外公开的uri就是content://开头的。

说是这么说,什么场景下我们会需要访问其他应用的私有文件或让其他应用访问自己的私有文件呢?有几个场景还是比较常见的,比如,调用系统的安装程序安装apk文件(假设这个apk文件存放在私有目录中),那么就需要提供此apk的路径让安装程序来访问(这就是让其他应用访问自己的私有文件),又或者调用系统相机拍照时,需要提供一个拍照后的相片的存放路径等。下面以拍照为例子讲解具体的适配流程。

适配流程:

1.导入v4包,在主module的build.gradle下的dependencies节点下添加依赖,版本号视情况而定。

 implementation 'com.android.support:support-v4:27.1.1'

2.在 res/xml 目录下新建一个 xml 文件,用于存放应用需要共享的文件目录,这里我命名为file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path
        name="my_images"
        path="./images/"/>
</paths>

name是自定义的一个别名,path是这个共享的目录,这里的path值表示共享外部私有目录file/images下的文件。如果是输入的是"."则表示共享外部私有目录下的file/目录下的所有文件。
其中<paths>元素有以下几个子节点:

  • <files-path>:内部存储空间应用私有目录下的 files/ 目录,等同于 Context.getFilesDir() 所获取的目录路径
  • <cache-path>:内部存储空间应用私有目录下的 cache/ 目录,同于 Context.getCacheDir() 所获取的目录路径;
  • <external-path>:外部存储空间根目录,等同于 Environment.getExternalStorageDirectory() 所获取的目录路径;
  • <external-files-path>外部存储空间应用私有目录下的 files/ 目录,等同于 Context.getExternalFilesDir(null) 所获取的目录路径;
  • <external-cache-path>:外部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getExternalCacheDir();

3.在AndroidManifest中声明FileProvide:

  <provider
         android:name="android.support.v4.content.FileProvider"
         android:authorities="${applicationId}.FileProvider"
         android:exported="false"
         android:grantUriPermissions="true">
     <meta-data
         android:name="android.support.FILE_PROVIDER_PATHS"
         android:resource="@xml/file_paths" />
   </provider>

authorities的名字可自定义,一般为包名+FileProvide,resource就是刚刚新建的共享文件。

完成以上三步就ok了,下面来尝试调用系统相机进行拍照(注意CAMERA权限的申请)。
下面是测试代码:

private void testCapture() {
       String path = getExternalFilesDir(null) + File.separator + "images" + File.separator +
               System.currentTimeMillis() + ".jpg";
       File file = new File(path);
       if (!file.exists()) {
           file.mkdirs();
       }
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
           Uri uri = FileProvider.getUriForFile(this,
                   BuildConfig.APPLICATION_ID + ".FileProvider", file);
           Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
           intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
           startActivityForResult(intent, MY_REQUEST_CODE);
       } else {
            //7.0之前的使用
       }
   }

ok,正常打开相机:
7.0打开系统相机测试

点击✓后照片就会保存在指定的目录中,我们通过adb找到该文件:

查找照片

完美!!!这样就可以在7.0以上的设备访问应用私有文件了。


8.0—自适应图标

    由于不同的厂商对应用图标的形状有了一定的规范(圆角、圆形等),如果不遵循它们的规范,您的应用图标可能就会被强制改成它们要求的形状,有时候改过的图标可能你的期望落差太大 。所以从Android 8.0系统开始,google对应用图标进行统一的规范。在8.0版本之后,应用程序的图标被分为了两层:前景层和背景层。前景层是一个背景透明的logo,背景层一般是一张纯色或带纹理的图片。前景层和背景层组合之后,会被盖上一层mask,这层mask是厂商决定的,这样一来,不管这一层mask是圆角还是圆形,都可以完整地显示您的logo了。下面是官方文档的一张gif演示图。

8.0应用图标适配

适配流程
如果您的Android Studio版本在3.0以上,那么基本在新建工程的时候就会默认创建res/mipmap-anydpi-v26文件夹,如下:
mipmap-anydpi-v26
mipmap-anydpi-v26下有两个xml文件代表8.0以上版本的启动图标,没错是xml文件。打开ic_launcher.xml可以看到以下内容:

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

这是8.0版本的标准写法,由标签名即可知道其意思, <background >就是背景
<foreground >就是前景。这是新建工程后Android Studio为我们创建的,那么,我们可以根据项目的进行简单的更改即可。Android Studio3.0以上版本还为我们提供了一个可视化的工具Image Asset,windows下可用快捷键ctrl+shift+a搜索

Image Asset

这里提供了可以直接操作的可视化界面

Image Asset

这个没什么好说的,非常的简单。唯一要注意的是Preview界面上的黑色圆圈,如果您不想您的logo在mark的时候被覆盖掉,那么前景层必须显示在黑色圆圈内。这里我将默认的icon的背景层用一个黄色的<color>来替代。下载到手机上看下效果:

这里写图片描述
到这一步就已经完成了8.0的图标适配了。


8.0—通知渠道

    Android 8.0对通知栏进行了比较大的改动,引入了通知渠道的概念,就好像为每种不同的消息分类别一样,例如,某个新闻app,将通知分为两种,一种为新闻类通知,一种为广告类通知。这样,就可以创建两个不同的渠道,发出通知的时候,可以根据渠道来发送,这样做的好处是便于用户去管理每个渠道的通知(选择他们感兴趣的内容)。同时,Android用户具有管理每个渠道的权限(设置声音、震动等),并且可以关闭某个渠道的通知,附上官方文档。我们先来看一下最右app的通知渠道体验一下。
这里写图片描述
好了,现在我们来进行通知栏的适配。

适配流程
1.创建通知渠道

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel newsChannel = new NotificationChannel(CHANNEL_NEWS, "新闻",
                    NotificationManager.IMPORTANCE_HIGH);
            NotificationChannel adsChannel = new NotificationChannel(CHANNEL_ADS, "广告",
                    NotificationManager.IMPORTANCE_DEFAULT);
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            manager.createNotificationChannel(newsChannel);
            manager.createNotificationChannel(adsChannel);
    }

NotificationChannel对象就代表着一个通知渠道,构造方法接受三个参数,第一个参数是全局唯一的渠道id,String类型。第二个是渠道名称,注意这个名称给用户看的。第三个是通知的重要等级。这里我们调用createNotificationChannel()创建了两个渠道,新闻和广告。注意:通知渠道不会重复创建,创建过程可以放在Application中进行。运行上面的代码后,渠道就创建好了,如下图:
通知渠道

2.发送通知
我们定义三个按钮,一个用来发送新闻类的通知,一个用来发送广告类的通知,一个用来发送没有通知渠道的通知:
通知渠道测试

代码如下:

public void sendNews(View view) {
     NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     Notification notification = new NotificationCompat.Builder(this, CHANNEL_NEWS)
             .setContentTitle("法国获得2018世界杯冠军")
             .setContentText("法国夺冠克罗地亚虽败犹荣比利时季军")
             .setAutoCancel(true)
             .build();
     manager.notify(1, notification);
 }

 public void sendAds(View view) {
     NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     Notification notification = new NotificationCompat.Builder(this, CHANNEL_ADS)
             .setContentTitle("收到某新闻来自拼多多的广告")
             .setContentText("这是它的广告内容...")
             .setAutoCancel(true)
             .build();
     manager.notify(1, notification);
 }

 public void sendNoChannel(View view) {
     NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     Notification notification = new NotificationCompat.Builder(this)
             .setContentTitle("没有渠道的通知")
             .setContentText("通内容")
             .setAutoCancel(true)
             .setSmallIcon(R.mipmap.ic_launcher)
             .build();
     manager.notify(1, notification);
 }

运行效果:
通知渠道运行
可以看到,在8.0之后(targetSdkVersion=26)发送通知如果不指定渠道的话是无法发送出去的,只有指定了通知渠道的通知才可以正常发送,通知展示的方式取决于用户的设置。

总结

    到这里,6.0到8.0的系统适配内容就讲完了,由于一次性要讲三个大版本的适配,所以对一些内容叙述的不太完整,见谅。本次适配内容,也是参考了多个博主的文章,下面把链接放上来。

1. 关于 Android 7.0 适配中 FileProvider 部分的总结
2 .Android应用图标微技巧,8.0系统中应用图标的适配
3 .Android通知栏微技巧,8.0系统中通知栏的适配

猜你喜欢

转载自blog.csdn.net/weixin_38261570/article/details/81046352