总所周知从Android6.0之后的每一次系统迭代,系统特性都有比较大的改变。平时做开发的时候都是有在注意这些方面的适配,但是一直没有时间去整个一下,今天正好来总结一下吧。如果觉的太长,可以等下一篇文章,会出一个我们在应用中会遇到的,以及适配方案,其中不会详细介绍特性,只讲适配

Android6.0

1、动态权限

在Android 6.0之后,我们在使用很多的权限的时候都需要动态去申请权限,在此之前呢,我们只需要在manifest文件中申请权限就可以了。 

以下是需要单独申请的权限,共分为9组,每组只要有一个权限申请成功了,就默认整组权限都可以使用了

联系人 group:android.permission-group.CONTACTS

permission:android.permission.WRITE_CONTACTS  //写入联系人,但不可读取
        
  permission:android.permission.GET_ACCOUNTS  //访问GMail账户列表
        
  permission:android.permission.READ_CONTACTS  //允许应用访问联系人通讯录信息

手机 group:android.permission-group.PHONE

permission:android.permission.READ_CALL_LOG  //读取通话记录     
           
  permission:android.permission.WRITE_CALL_LOG  //写入通话记录
        
  permission:android.permission.READ_PHONE_STATE  //访问电话状态 
        
  permission:android.permission.CALL_PHONE  //允许程序从非系统拨号器里输入电话号码
        
  permission:android.permission.USE_SIP  //允许程序使用SIP视频服务
        
  permission:android.permission.PROCESS_OUTGOING_CALLS  //允许程序监视,修改或放弃播出电话
        
  permission:com.android.voicemail.permission.ADD_VOICEMAIL  //添加语音信箱

日程  group:android.permission-group.CALENDAR

permission:android.permission.READ_CALENDAR  //允许程序读取用户的日程信息
        
  permission:android.permission.WRITE_CALENDAR  //写入日程,但不可读取

相机 group:android.permission-group.CAMERA

permission:android.permission.CAMERA  //允许访问摄像头进行拍照

传感器  group:android.permission-group.SENSORS

permission:android.permission.BODY_SENSORS     //人体传感器

定位 group:android.permission-group.LOCATION

permission:android.permission.ACCESS_FINE_LOCATION  //通过GPS芯片接收卫星的定位信息,定位精度达10米以内
        
  permission:android.permission.ACCESS_COARSE_LOCATION  //通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米

存储 group:android.permission-group.STORAGE

permission:android.permission.READ_EXTERNAL_STORAGE  //允许程序读取外部存储,如SD卡上读文件
        
  permission:android.permission.WRITE_EXTERNAL_STORAGE  //允许程序写入外部存储,如SD卡上写文件

录音 group:android.permission-group.MICROPHONE

permission:android.permission.RECORD_AUDIO  //录制声音通过手机或耳机的麦克

短信 group:android.permission-group.SMS

permission:android.permission.READ_SMS  //读取短信内容

  permission:android.permission.SEND_SMS  //发送短信
           
  permission:android.permission.RECEIVE_WAP_PUSH  //接收WAP PUSH信息
        
  permission:android.permission.RECEIVE_MMS  //接收彩信

  permission:android.permission.RECEIVE_SMS  //接收短信
   
  permission:android.permission.READ_CELL_BROADCASTS

以下是普通权限只需要在清单文件中配置就可以直接使用了

网络

android.permission.INTERNET  //访问网络连接,可能产生GPRS流量

  android.permission.ACCESS_NETWORK_STATE  //获取网络信息状态,如当前的网络连接是否有效

  android.permission.CHANGE_NETWORK_STATE  //改变网络状态如是否能联网

  android.permission.CHANGE_WIFI_MULTICAST_STATE  //改变WiFi多播状态

  android.permission.CHANGE_WIFI_STATE //改变WiFi状态

  android.permission.ACCESS_WIFI_STATE  //获取当前WiFi接入的状态以及WLAN热点的信息

蓝牙

android.permission.BLUETOOTH  //允许程序连接配对过的蓝牙设备

  android.permission.BLUETOOTH_ADMIN  //允许程序进行发现和配对新的蓝牙设备

状态

android.permission.RECEIVE_BOOT_COMPLETED  //允许程序开机自动运行

  android.permission.DISABLE_KEYGUARD  //允许程序禁用键盘锁

  android.permission.MODIFY_AUDIO_SETTINGS  //修改声音设置信息

  com.android.alarm.permission.SET_ALARM  //设置闹铃提醒

  android.permission.SET_TIME_ZONE  //设置系统时区

  android.permission.VIBRATE  //允许振动

  android.permission.NFC  //允许程序执行NFC近距离通讯操作,用于移动支持

  android.permission.EXPAND_STATUS_BAR  //允许程序扩展或收缩状态栏

  android.permission.ACCESS_LOCATION_EXTRA_COMMANDS  //允许程序访问额外的定位提供者指令

  android.permission.BROADCAST_STICKY  //允许一个程序收到广播后快速收到下一个广播

  android.permission.FLASHLIGHT  //允许访问闪光灯

  android.permission.GET_PACKAGE_SIZE  //获取应用的文件大小

  android.permission.KILL_BACKGROUND_PROCESSES  //允许程序调用killBackgroundProcesses(String).方法结束后台进程

  android.permission.WAKE_LOCK  //允许程序在手机屏幕关闭后后台进程仍然运行

2、使用Builder模式来构建通知

在Android 6.0之后此版本移除了Notification.setLatestEventInfo()方法。用Notification.Builder类来构造通知,在需要反复更新通知的情况下,保存并重用Notification.Builder的实例;在获取更新后的Notification实例时,调用其build()方法。我们要创建一个通知是这样的

private void addNotification() {

        Intent intent = new Intent(this, Main2Activity.class); //点击了之后进入的一个Actity
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);

        Notification noti = new Notification.Builder(this)
                .setTicker("你懂的!")
                .setContentTitle("短信通知")
                .setContentText("亲爱的,晚上8点老地方见哦~")
                .setSmallIcon(R.drawable.icon)//设置图标     
                .setDefaults(Notification.DEFAULT_SOUND)//设置声音
                .setContentIntent(pendingIntent)//点击之后的页面
                .build();

        NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
        manager.notify(666,noti);
    }

取消一个通知是这样的

NotificationManager manager =  (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                manager.cancel(R.drawable.icon);
                manager.cancel(666);
                Toast.makeText(getApplication(), "Notification cancled", Toast.LENGTH_SHORT).show();

这个时候取消的id和创建的时候的id要一致。那么其实在这个通知其实在8.0之后呢又有改变,我们后面在说,需要加入通道。

3、取消了Apache HTTP客户端

Android6.0版本移除了对Appache的HTTP client的支持。如果你的app的目标版本是Android2.3(API level 9)或者更高,请使用HttpURLConnection类进行替换。此类采用了透明压缩(transparent compression)和响应缓存(response caching),最小化电量消耗。如果你希望继续使用Appache Http API,请修改你的build.gradle文件,增加如下:

android {
    useLibrary 'org.apache.http.legacy'
}

此library在本地的sdk中:sdk\platforms\android-23\optional

4、低耗电模式,设备处于空闲状态,推迟cpu和网络活动

此次发布引入了新的省电优化方案,在设备和应用空闲时起作用。此特性影响所有的应用,请确保做好相应的测试。

    休眠模式(Doze): 手机在没有外接电源且放置至屏幕关闭一段时间后,设备将进入休眠模式,它将使系统进入睡眠状态(sleep state)。在此模式下,设备周期性的短暂恢复正常操作,以使应用可以同步,系统执行一些必要操作

    应用待机模式(App Standby):在用户没有使用某应用的情况下,此模式允许系统判定此应用进入空闲状态。系统的判定依据则是用户在一定的时间内没有再点击此应用(译注:主要还是依据前台进程判定)。如果此时设备也没有连接充电器,系统将禁用该应用的网络连接、同步及任务调度

5、密钥库变更,不在支持DSA,但是依旧支持ECDSA

此版本上Android Keystore provider不再支持DSA,仍旧支持ECDSA。
  锁屏密码在(如用户或设备管理器)禁用或重置的情况下,不需要加密部分将不再被删除,而加密部分则会被删除。

6、相机Camera变更为Camera2.之前是先到先得,现在是按照优先级别使用

 在此发布版本上,访问相机服务模型将由原来的“先来先服务”方式,改为基于优先级的访问方式。服务行为的变化包括:
  (1)客户端应用进程基于优先级的方式访问相机子系统,包括打开和配置设备相机。用户可见的应用进程通常被赋予高优先级,使得相机资源的获取和使用更可靠。
  (2)高优先级的应用能够“驱逐”低优先级应用,而使用相机(译注:高优先级应用能以抢占方式使用相机)
  (3)在有合适的相机的硬件上,多个应用进程可以同时且独立地使用相机设备。但是,多进程同时访问的情况下,会导致相机设备的性能显著降低,而现在相机服务会检测并不允许此种情况发生。这种变化导致低优先级的应用被“驱逐”,直到没有其它应用直接访问同一个相机设备。
  (4)改变当前使用者(译注:多用户情况下的用户切换)后,引起之前使用者所拥有的应用无法再使用相机。访问相机被限制在设备的当前使用者上。实际上,这意味着一个“guest”用户账户在切换到其它用户账户时,不能再保留一个运行的进程访问相机服务。

关于6.0的特性我们就先说到这里,当然还有一些特性我们没有列出来,如果大家感兴趣的话可以上官网看看。

Android 7.0

1、权限更改

随着Android版本越来越高,Android对隐私的保护力度也越来越大。从Android6.0引入的动态权限控制(Runtime Permissions)到Android7.0的“私有目录被限制访问”。在Android7.0之前的应用可以读写手机存储中任何一个目录及文件,这也带来了很多的安全问题。现在Android也在着力解决这一问题。所以在android7.0中直接使用真实的路径的Uri会被认为是不安全的,会抛出一个FileUriExposedException这样的异常。需要使用FileProvider,选择性地将封装过的Uri共享到外部。

那么我需要在程序中怎么做呢?首先由于FileProvider是继承ContentProvider,属于四大组件之一,需要在AndroidManifest.xml中配置,配置如下:

<!--版本更新所要用到的 fileProvider 用于兼容7.0-->
    <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_provider_paths"/>
    </provider>
  • name:provider的类名,若使用默认的v4的FileProvider可使用”android.support.v4.content.FileProvider”,也可以设置为自定义的继承FileProvider的provider类;
  • authorities:一个签名认证,可以自定义,但在获取uri的时候需要保持一致;
  • grantUriPermissions:使用FileProvider的使用需要我们给流出的URI赋予临时访问权限(READ和WRITE),该设置是允许我们行使该项权力;
  • meta-data:meta-data配置的是我们可以访问的文件的路径配置信息,需要使用xml文件进行配置,FileProvider会通过解析xml文件获取配置项,其中name名字不可改变为:android.support.FILE_PROVIDER_PATHS,resource为配置路径信息的配置项目。

标签中的resource填写配置fileprovider的配置文件,在res资源目录下新建xml文件下,在该文件夹下创建file_provider_paths.xml文件,这个xml文件名并不是一定要这么起,只要和清单文件中配置的文件名一致就行。

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <paths>
    <!-- Context.getFilesDir() + "/path/" -->
    <files-path
        name="my_files"
        path="mazaiting/"/>
    <!-- Context.getCacheDir() + "/path/" -->
    <cache-path
        name="my_cache"
        path="mazaiting/"/>
    <!-- Context.getExternalFilesDir(null) + "/path/" -->
    <external-files-path
        name="external-files-path"
        path="mazaiting/"/>
    <!-- Context.getExternalCacheDir() + "/path/" -->
    <external-cache-path 
         name="name" 
         path="mazaiting/" />
    <!-- Environment.getExternalStorageDirectory() + "/path/" -->
    <external-path
        name="my_external_path"
        path="mazaiting/"/>
    <!-- Environment.getExternalStorageDirectory() + "/path/" -->
    <external-path
        name="files_root"
        path="Android/data/<包名>/"/>
    <!-- path设置为'.'时代表整个存储卡 Environment.getExternalStorageDirectory() + "/path/"   -->
    <external-path
        name="external_storage_root"
        path="."/>
  </paths>
</resources>

<path> 元素必须包含一到多个子元素。这些子元素用于指定共享文件的目录路径,必须是这些元素之一:

  • <files-path>:内部存储空间应用私有目录下的 files/ 目录,等同于 Context.getFilesDir() 所获取的目录路径/data/data/< package name >/files;
  • <cache-path>:内部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getCacheDir() 所获取的目录路径/data/data/< package name >/cache;
  • <external-path>:外部存储空间根目录,等同于 Environment.getExternalStorageDirectory() 所获取的目录路径/mnt/sdcard/;
  • <external-files-path>:外部存储空间应用私有目录下的 files/ 目录,等同于 Context.getExternalFilesDir(null) 所获取的目录路径SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据;
  • <external-cache-path>:外部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getExternalCacheDir()获取的路径sdcard/android/data/你的应用包名/cache/目录,一般存放临时缓存数据.如果使用上面的方法,当你的应用在被用户卸载后,SDCard/Android/data/你的应用的包名/ 这个目录下的所有文件都会被删除,不会留下垃圾信息;

补充知识点:

Environment.getDataDirectory() = /data
Environment.getDownloadCacheDirectory() = /cache
Environment.getExternalStorageDirectory() = /mnt/sdcard
Environment.getExternalStoragePublicDirectory(“test”) = /mnt/sdcard/test
Environment.getRootDirectory() = /system
getPackageCodePath() = /data/app/com.my.app-1.apk
getPackageResourcePath() = /data/app/com.my.app-1.apk
getCacheDir() = /data/data/com.my.app/cache
getDatabasePath(“test”) = /data/data/com.my.app/databases/test
getDir(“test”, Context.MODE_PRIVATE) = /data/data/com.my.app/app_test
getExternalCacheDir() = /mnt/sdcard/Android/data/com.my.app/cache
getExternalFilesDir(“test”) = /mnt/sdcard/Android/data/com.my.app/files/test
getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files
getFilesDir() = /data/data/com.my.app/files

我们在清单文件中配置后该怎么使用?

1、生成Uri

 Uri contentUri = FileProvider.getUriForFile(context,
              BuildConfig.APPLICATION_ID + ".fileProvider",
              new File(path));

2、授予临时权限

      intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

3、传递

      intent.setDataAndType(contentUri, "application/vnd.android.package-archive");

我们以安装为例看看。

......
 
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 
        //判读版本是否在7.0以上
        Uri apkUri =
                    FileProvider.getUriForFile(context, 
                    "com.chaychan.demo" + ".fileprovider", 
                    file);
        //添加这一句表示对目标应用临时授权该Uri所代表的文件
        installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
    } else {
        installIntent.setDataAndType(Uri.fromFile(file),
                "application/vnd.android.package-archive");
    }
         ......

,如果是Andorid7.0或以上,则不再使用Uri.fromFile()方法获取文件的Uri,而是通过使用FileProvider(support.v4提供的类)的getUriForFile()。

当然7.0还有很多特性比如新增了多窗口显示;通知加强;删除了网络变化CONNECTIVITY_ACTION 广播,拍照录像的ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 广播三项隐式广播等等等等,官网和其他网站上很多相关的博文,我就不赘述了,后面给大家帖参考链接。

Android8.0

1、通知

在前面6.0的时候我就说到过在8.0的时候通知发生了变化。

Android 8.0(API 级别 26)添加了对通知渠道的支持,即应用可以将其通知整理划分到不同的主题类别中。每种类别都有自己的提醒类型,用户可以根据自己的兴趣选择性地启用或停用这些类别。

适配方法如下:

private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        NotificationManager notificationManager = (NotificationManager)
                getSystemService(Context.NOTIFICATION_SERVICE);

        //分组(可选)
        //groupId要唯一
        String groupId = "group_001";
        NotificationChannelGroup group = new NotificationChannelGroup(groupId, "广告");

        //创建group
        notificationManager.createNotificationChannelGroup(group);

        //channelId要唯一
        String channelId = "channel_001";

        NotificationChannel adChannel = new NotificationChannel(channelId,
                "推广信息", NotificationManager.IMPORTANCE_DEFAULT);
        //补充channel的含义(可选)
        adChannel.setDescription("推广信息");
        //将渠道添加进组(先创建组才能添加)
        adChannel.setGroup(groupId);
        //创建channel
        notificationManager.createNotificationChannel(adChannel);

        //创建通知时,标记你的渠道id
        Notification notification = new Notification.Builder(MainActivity.this, channelId)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentTitle("一条新通知")
                .setContentText("这是一条测试消息")
                .setAutoCancel(true)
                .build();
        notificationManager.notify(1, notification);

    }
}

2、后台限制执行

应用在两个方面受到限制:

后台服务限制: 处于空闲状态时,应用可以使用的后台服务存在限制。 这些限制不适用于前台服务,因为前台服务更容易引起用户注意。

广播限制: 除了有限的例外情况,应用无法使用清单注册隐式广播。 它们仍然可以在运行时注册这些广播,并且可以使用清单注册专门针对它们的显式广播。

3、运行时权限

在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。

对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。

4、安装未知应用的权限

首先在AndroidManifest.xml文件中声明:

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

然后在需要调用安装的地方调用方法判断是否取得权限:

getPackageManager().canRequestPackageInstalls();

如果没有,就引导用户打开权限

Intent intent =new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

startActivity(intent);

然后这个权限打开之后呢我们是在activity中通过onActivityResult来接受结果的,当resultCode== Activity.RESULT_OK并且requestCode是我们申请权限的请求码的时候我们就可以进行我们的安装操作了。

5、悬浮窗

 如果在dialog中setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);使用了SYSTEM_ALERT_WINDOW,并且尝试使用以下类型的提示窗:

TYPE_PHONE

TYPE_PRIORITY_PHONE

TYPE_SYSTEM_ALERT

TYPE_SYSTEM_OVERLAY

TYPE_SYSTEM_ERROR

TYPE_TOAST

这个时候是不可以的,在Android8.0中要使用系统级别的弹框应用必须使用名为 TYPE_APPLICATION_OVERLAY 的新窗口类型

  • 应用的提醒窗口始终显示在状态栏和输入法等关键系统窗口的下面。
  • 系统可以移动使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口或调整其大小,以改善屏幕显示效果。
  • 通过打开通知栏,用户可以访问设置来阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的提醒窗口。
// 设置该Dialog为系统级别的
if(Build.VERSION.SDK_INT >= 26) {
   mDialog.getWindow().setType(
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
 }else {
   mDialog.getWindow().setType(
                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
 }

当然记得需要有权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
//8.0之后的权限申请要用下面这种方式,所以这两个权限都要加上
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />

然后我们还要为悬浮窗开启动态权限,这个权限是会通过intent进入设置页面进行设置的。每个系统可能存在一定的差异。

6、网页表单自动填充

现在,Android 自动填充框架提供对自动填充功能的内置支持,对于安装到运行 Android 8.0 的设备上的应用,与 WebView 对象相关的下列函数已经发生变化:

WebSettings

- getSaveFormData() 函数现在返回 false。之前,此函数返回 true。

- 调用 setSaveFormData() 不再有任何效果。

WebViewDatabase

- 调用 clearFormData() 不再有任何效果。

- hasFormData() 函数现在返回 false。之前,当表单包含数据时,此函数返回 true。

7、集合的处理

在 Android 8.0 中,Collections.sort() 是在 List.sort() 的基础上实现的。在 Android 7.x(API 级别 24 和 25)中,则恰恰相反。在过去,List.sort() 的默认实现会调用 Collections.sort()。

  • List.sort() 的实现不能调用 Collections.sort(),因为这会导致堆栈因无限递归而溢出。相反,如果您需要 List 实现的默认行为,应避免重写 sort()。
    此项变更使 Collections.sort() 可以利用优化的 List.sort() 实现,但具有以下限制:
  • 如果父类以不适当的方法实现 sort() ,通常最好使用在 List.toArray()、Arrays.sort() 和 ListIterator.set() 的基础上构建的实现重写 List.sort()。
  • 如果您选择后者只是因为您希望开发一种适用于所有 API 级别的 sort() 函数,可以考虑赋予其一个唯一的名称,例如 sortCompat(),而不是重写 sort()。
  • 现在,Collections.sort() 只是对调用 sort() 的 List 实现进行的一项结构性修改。例如,在 Android 8.0 之前的平台版本中,如果通过调用 List.sort() 进行排序,则当迭代处理 ArrayList 以及在迭代过程中调用 sort() 时,会引发 ConcurrentModificationException。而 Collections.sort() 则不会引发异常。
    此项变更使平台行为更加一致:现在,两种方法都会引发 ConcurrentModificationException。

现在,AbstractCollection.removeAll()AbstractCollection.retainAll() 始终引发 NullPointerException;之前,当集合为空时不会引发 NullPointerException 所以我们需要做判空处理。。

Android9.0

1、刘海屏适配

Android 9 支持最新的全面屏,其中包含为摄像头和扬声器预留空间的屏幕缺口。 通过 DisplayCutout类可确定非功能区域的位置和形状,这些区域不应显示内容。 要确定这些屏幕缺口区域是否存在及其位置,使用 getDisplayCutout()函数。

2、通知功能的变更

Android 8.0 引入了通知渠道,允许您为要显示的每种通知类型创建可由用户自定义的渠道。 Android 9 通过下列变更简化通知渠道设置:

  • 屏蔽渠道组:现在,用户可以针对某个应用在通知设置中屏蔽整个渠道组。 您可以使用 isBlocked() 函数确定何时屏蔽一个渠道组,从而不会向该组中的渠道发送任何通知。
    此外,您的应用可以使用全新的 getNotificationChannelGroup() 函数查询当前渠道组设置。
  • 全新的广播 Intent 类型:现在,当通知渠道和渠道组的屏蔽状态发生变更时,Android 系统将发送广播 Intent。 拥有已屏蔽的渠道或渠道组的应用可以侦听这些 Intent 并做出相应的回应。 有关这些 Intent 操作和 extra 的更多信息,请参阅 NotificationManager 参考中更新的常量列表。 有关响应广播 Intent 的信息,请参阅广播。
  • NotificationManager.Policy 有 3 种新的“请勿打扰”优先级类别:
  • PRIORITY_CATEGORY_ALARMS优先处理警报。
  • PRIORITY_CATEGORY_MEDIA 优先处理媒体源的声音,如媒体和语音导航。
  • PRIORITY_CATEGORY_SYSTEM 优先处理系统声音。
  • NotificationManager.Policy 还有 7 种新的“请勿打扰”常量,可以用来抑制视觉中断:
  • SUPPRESSED_EFFECT_FULL_SCREEN_INTENT 防止通知启动全屏 Activity。
  • SUPPRESSED_EFFECT_LIGHTS 屏蔽通知灯。
  • SUPPRESSED_EFFECT_PEEK 防止通知短暂进入视图(“滑出”)。
  • SUPPRESSED_EFFECT_STATUS_BAR 防止通知显示在支持状态栏的设备的状态栏中。
  • SUPPRESSED_EFFECT_BADGE 在支持标志的设备上屏蔽标志。 如需了解详细信息,请参阅修改通知标志。
  • SUPPRESSED_EFFECT_AMBIENT 在支持微光显示的设备上屏蔽通知。
  • SUPPRESSED_EFFECT_NOTIFICATION_LIST 防止通知显示在支持列表视图(如通知栏或锁屏)的设备的列表视图中。

3、利用 Wi-Fi RTT 进行室内定位

Android 9新增了对IEEE 802.11mc Wi-Fi 协议(也就是RTT)的平台支持,这可以让开发者更精确地进行室内定位,对于一些使用LBS服务的的应用来说,这是个利好消息。

跟RTT定位有关的API主要在 android.net.wifi.rtt包内,使用RTT定位,首先需要获取WifiRttManager:

Context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE)

一些API需要获取ACCESS_WIFI_STATE,CHANGE_WIFI_STATE,ACCESS_FINE_LOCATION等权限

当然,使用这个API也需要设备支持WIFI定位。

Tips:不是所有设备都支持RTT定位。假如你的应用只支持WIFI RTT定位才能运行,那么发布在Google Play上时只能由支持此类功能的设备才能下载。且需要如下声明:

<manifest ...>
    <uses-feature android:name="android.hardware.wifi.rtt" />
    ...
</manifest>

若不使用RTT也能正常运行,可以在代码中这样监测是否能使用RTT定位:

getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)

4、非activity中启动activity

强制执行 FLAG_ACTIVITY_NEW_TASK :在 Android 9 中,您不能从非 Activity 环境中启动 Activity,除非您传递 Intent 标志 FLAG_ACTIVITY_NEW_TASK。 如果您尝试在不传递此标志的情况下启动 Activity,则该 Activity 不会启动,系统会在日志中输出一则消息。

5、http网络请求

在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。

有以下三种解决方案

  1. APP改用https请求
  2. targetSdkVersion 降到27以下
  3. 在 res 下新增一个 xml 目录,然后创建一个名为:network_security_config.xml 文件(名字自定) ,内容如下,大概意思就是允许开启http请求
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

然后在APP的AndroidManifest.xml文件下的application标签增加以下属性:

android:networkSecurityConfig="@xml/network_security_config"

6、前台服务

针对 Android 9 或更高版本并使用前台服务的应用必须请求 FOREGROUND_SERVICE 权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。

7、non-sdk 接口限制

可以使用命令扫描整个app里面存在的非 SDK 接口:

1appcompat.sh --dex-file=apk路径
  • 白名单:即SDK
  • 浅灰名单:仍可以访问的非 SDK 函数/字段
  • 深灰名单:对于目标 SDK 低于 API 级别 28 的应用,允许使用深灰名单接口; 对于目标 SDK 为 API 28 或更高级别的应用:行为与黑名单相同
  • 黑名单:受限,无论目标 SDK 如何,平台将表现为似乎接口并不存在

列入浅灰名单的非 SDK 接口包含可以在 Android 9 中继续工作的函数和字段,但不能保证在未来版本的平台中能够继续访问,主要需要关注深灰名单和黑名单,需要找到可以替代的SDK接口进行适配。网上有人发现了绕过API检查的方法,也有专门的库允许在Android P上使用反射而没有任何限制,如FreeReflection:

//允许在Android P上使用反射而不受任何限制
implementation 'me.weishu:free_reflection:1.2.0'

//在App.java中加入即可:
Reflection.unseal(this);

8、不允许共享WebView数据目录

应用程序不能再跨进程共享单个WebView数据目录。如果您的应用有多个使用WebView,CookieManager或android.webkit包中的其他API的进程,则当第二个进程调用WebView方法时,您的应用将崩溃。

该特性只影响已经适配P的应用,也就是targetSDK Version>=P。

9、移除对 Build.serial 的直接访问

现在,需要 Build.serial 标识符的应用必须请求 READ_PHONE_STATE 权限,然后使用 Android P 中新增的新 Build.getSerial() 函数

Android10.0

AndroidQ引入了大量更改和限制以增强对用户隐私的保护。

权限

受影响应用

如何启用(影响范围)

存储权限

访问和共享外部存储设备中的文件的应用

adb shell sm set-isolated-storage on(下文详述)

定位权限

在后台时请求访问用户位置信息的应用 

这种权限策略在 Android Q 上始终处于启用状态

从后台启动 Activity

不需要用户互动就启动 Activity 的应用

关闭允许系统执行后台活动开发者选项即可启用限制

设备标识符(deviceId)

访问设备序列号或 IMEI 的应用

在搭载 Android Q 的设备上安装应用

无线扫描权限

使用 WLAN API 和 Bluetooth API 的应用

以 Android Q 为目标平台

1、存储权限

Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。

沙盒,简单而言就是应用专属文件夹,并且访问这个文件夹无需权限。谷歌官方推荐应用在沙盒内存储文件的地址为Context.getExternalFilesDir()下的文件夹。比如要存储一张图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。

以下将按访问的目标文件的地址介绍如何适配:

  • 访问自己文件:Q中用更精细的媒体特定权限替换并取消了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己沙盒中的文件。
  • 访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申请方法同原来的存储权限(我今天发现这三个权限已经舍去了,还是用以前的权限READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE)。
  • 访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。
  • 访问其他应用沙盒文件:关于作用域的问题我们还是去看看郭神的文章吧

对应App的数据, 需要存储到App的沙盒中, 对应的路径为下:

视频文件    context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
音频文件    context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)
图片文件    context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)

  • 存储到沙盒中的这些地址的实际路径在/data/user/0/包名/中,所以这些文件在App卸载之后会被清除
  • 在这些目录里的文件受系统保护, 其他App无法直接对其访问

2、定位权限

为了让用户更好地控制应用对位置信息的访问权限,Android Q 引入了新的位置权限 ACCESS_BACKGROUND_LOCATION。与现有的 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。除非应用的某个 Activity 可见或应用正在运行前台服务,否则应用将被视为在后台运行。如果应用需要在后台时也获得用户位置(比如滴滴),就需要动态申请ACCESS_BACKGROUND_LOCATION权限。

targetSDK <= P 应用如果请求了ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限,Q设备会自动帮你申请ACCESS_BACKGROUND_LOCATION权限。

3、设备唯一标识符

从 Android Q 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 签名权限才能访问设备的不可重置标识符(包含 IMEI 和序列号)。原来的READ_PHONE_STATE权限已经不能获得IMEI和序列号,如果想在Q设备上通过((TelephonyManager) getActivity()      .getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId()获得设备ID,会返回空值(targetSDK<=P)或者报错(targetSDK==Q)。且官方所说的READ_PRIVILEGED_PHONE_STATE权限只提供给系统app,所以这个方法算是废了。

 

5、 后台App启动限制

Android10中, 当App无前台显示的Activity时,其启动Activity会被系统拦截, 导致启动无效。这样做可以让用户使用的过程中, 不被其他App强制打断,但是这样就会影响闹钟类, 带呼叫功能的App不太友好了。对此官方给予的折中方案是使用全屏Intent(full-screen intent), 既创建通知栏通知时, 加入full-screen intent 设置, 示例代码如下(基于官方文档修改):

Intent fullScreenIntent = new Intent(this, CallActivity.class);
PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
        fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
 
NotificationCompat.Builder notificationBuilder =
        new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Incoming call")
    .setContentText("(919) 555-1234")
    //以下为关键的3行
    .setPriority(NotificationCompat.PRIORITY_HIGH)
    .setCategory(NotificationCompat.CATEGORY_CALL)
    .setFullScreenIntent(fullScreenPendingIntent, true);
    
NotificationManager notifyManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notifyManager.notify(notifyId, builder.build());
复制代码

注意在Target SDk为29及以上时,需要在AndroidManifest上增加USE_FULL_SCREEN_INTENT申明

//AndroidManifest中
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />