聊聊Android M 6.0 的运行时权限

一.序

1.1 背景介绍.

为什么要聊这个话题呢?
从官网最新数据(2017.12.11)来看:

  • 现在大概有99.6%的用户Android版本是在4.0.3(API-15)以上
  • 并且6.0以上(API-23)的用户占比在53.5%,

官网版本分布截图

传送门最新Android版本分布.
Android 产品经理Edward Cunningham 发表文章: Improving app security and performance on Google Play for years to come 表示:

  • August 2018: New apps required to target API level 26 (Android 8.0) or higher.
  • November 2018: Updates to existing apps required to target API level 26 or higher.

所以我们先将权限部分适配target 26

1.2简单介绍下build.gradle

android {

    compileSdkVersion 27
    buildToolsVersion "27.0.2"

    defaultConfig {
        applicationId "***.***.***"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
}
  • targetSdkVersion:就是APP能够适配的系统的版本,意味着已经兼容到对应版本.Android Developer也有对应的介绍.告知开发者targetSdkVersion升级要注意什么.官网上兼容8.0的注意事项,大家有空可以自行研究.
  • compileSdkVersion:顾名思义,是编译时,使用的SDK版本,当然compileSdkVersion>= targetSdkVersion
  • minSdkVersion: 当然就是我们App支持的最低版本号.海外版大多数App都会选择支持14-15.国内,有些App可能会支持到9.
  • maxSdkVersion:很少见的属性.却在权限设置里会有一些应用场景.比如设置这个权限支持的最大版本号。

1.3 扩展:简单阐述一下maxSdkVersion的神奇之处

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="18"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="18"/>

当我们使用Environment.getExternalStorageDirectory()方法时,实际的存储位置是sdcard/Android/data/包名/,这个地址在Sd卡上。
我们在使用这个方法,实际在API-19以上是无需申请权限的。可以直接使用。所以假如我们不需要额外创建文件夹。可以无需申请STORAGE权限。标识了maxSdkVersion=18之后,表示,当手机版本高于18,这个权限将不会出现在权限列表了。

好处是什么?

当然,

  1. 我们申请的权限越少越好。
  2. 用户手机设备高于6.0之后。可以拒绝掉这个权限。那么Environment.getExternalStorageDirectory()将失效。
  3. 所以不出现就不能拒绝。我还是可以用。^^

二.权限简介

2.1 不同版本会产生的状况

  1. if(targetSdkVersion(App) < 23 && Build.VERSION.SDK_INT(手机版本) < 23)
    安装APP时,会声明App所需要的权限,不会询问用户.也不可以关闭权限(部分国产定制机型除外Xiaomi就在6.0之前就已经提出了一套自己的权限系统.)
  2. if(targetSdkVersion < 23 && Build.VERSION.SDK_INT >= 23)表示App并未兼容6.0,安装App时,也不会让用户动态申请权限,但是用户可以自行去设置页面关闭权限,
  3. if(targetSdkVersion >= 23 && Build.VERSION.SDK_INT < 23) 同1.所述,也是仅仅在安装App时提示,声明App所需要的权限,不会询问用户动态申请,也不可以关闭.
  4. if(targetSdkVersion >= 23 && Build.VERSION.SDK_INT >= 23) 动态申请权限.也是大势所趋.

    • 第3点讲到的情况

第三点讲到的情况.6.0以上的设备,安装targetSdkVersion<23的App时,也可以跳转到Setting页面自行关闭权限.但是会弹出提示,中文翻译是:此应用专为旧版Android打造.拒绝权限可能会导致其无法正常运行。

2.2权限分类

Android中有很多权限,但并非所有的权限都是敏感权限,于是6.0系统就对权限进行了分类,一般为下述几类

  • 正常(Normal)权限
  • 危险(Dangerous)权限
  • 特殊(signature)权限

2.2.1正常(Normal)权限

普通权限有很多,不一一列举了,总结一下他们的特点:

  • 对用户隐私没有较大影响或者不会打来安全问题。
  • 安装后就赋予这些权限,不需要显示提醒用户,用户也不能取消这些权限。

2.2.2危险(Dangerous)权限

Android Dangerous(危险)权限和权限组的划分。

public class Permissions {

  protected static String[] ABS_CALENDAR;
  protected static String[] ABS_CAMERA;
  protected static String[] ABS_CONTACTS;
  protected static String[] ABS_LOCATION;
  protected static String[] ABS_MICROPHONE;
  protected static String[] ABS_PHONE;
  protected static String[] ABS_SENSORS;
  protected static String[] ABS_SMS;
  protected static String[] ABS_STORAGE;

  static {

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      ABS_CALENDAR = new String[] {};
      ABS_CAMERA = new String[] {};
      ABS_CONTACTS = new String[] {};
      ABS_LOCATION = new String[] {};
      ABS_MICROPHONE = new String[] {};
      ABS_PHONE = new String[] {};
      ABS_SENSORS = new String[] {};
      ABS_SMS = new String[] {};
      ABS_STORAGE = new String[] {};
    } else {
      ABS_CALENDAR = new String[] {
          Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR
      };

      ABS_CAMERA = new String[] {
          Manifest.permission.CAMERA
      };

      ABS_CONTACTS = new String[] {
          Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS,
          Manifest.permission.GET_ACCOUNTS
      };

      ABS_LOCATION = new String[] {
          Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION
      };

      ABS_MICROPHONE = new String[] {
          Manifest.permission.RECORD_AUDIO
      };

      ABS_PHONE = new String[] {
          Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE,
          Manifest.permission.READ_CALL_LOG, Manifest.permission.WRITE_CALL_LOG,
          Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS
      };

      ABS_SENSORS = new String[] {
          Manifest.permission.BODY_SENSORS
      };

      ABS_SMS = new String[] {
          Manifest.permission.SEND_SMS, Manifest.permission.RECEIVE_SMS,
          Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_WAP_PUSH,
          Manifest.permission.RECEIVE_MMS
      };

      ABS_STORAGE = new String[] {
          Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE
      };
    }
  }
}

暂时分为9个权限组,这些权限,作为动态申请中,可能涉及到主要内容。(注:申请同一权限组内的子权限,系统弹出的权限申请框中的文案是一样的。)

2.2.3特殊(Signature)权限

这个分类呢,可以说对于用户来说,更危险的权限。特别敏感的权限。此类权限不能在App内弹出系统的权限申请框。只能跳转到设置页面修改。

  //修改系统设置
  <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
  //悬浮窗
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

1.修改系统设置-WRITE_SETTINGS

申请:

//申请权限跳转是可以使用startActivityForResult
public static void requestPermissionForResult(Activity activity, int requestCode) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || activity == null) {
      return;
    }
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
        Uri.parse("package:" + activity.getPackageName()));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    activity.startActivityForResult(intent, requestCode);
}

判断是否有该权限:

//我们可以在onActivityResult的时候判断权限是否有获取
public static boolean hasPermission(Context context) {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.System.canWrite(context);
}

2.系统悬浮窗-SYSTEM_ALERT_WINDOW

申请:

public static void requestPermissionForResult(Activity activity, int requestCode) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || activity == null) {
      return;
    }
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
        Uri.parse("package:" + activity.getPackageName()));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    activity.startActivityForResult(intent, requestCode);
}

判断是否有该权限:

public static boolean hasPermission(Context context) {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
}

三.常用API

注意:因为API为 23 以上新增的。所以使用的时候要判断版本号。

1.ContextCompat.checkSelfPermission

检测权限是否已经获取。

ContextCompat.checkSelfPermission(context, permission)!= PackageManager.PERMISSION_GRANTED

2.shouldShowRequestPermissionRationale(Activity)

//伪代码
public class Activity{

  ...

  public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
        return getPackageManager().shouldShowRequestPermissionRationale(permission);
    }

  ...

}

这个方法有点难懂,大意就是说,是否要告知用户申请权限的重要性。这里有点绕,我们这详细说明一下。

  1. 假设用户第一次申请前,这个方法的返回值false
  2. 假设用户申请被拒绝过一次之后,这个方法的返回值true
  3. 假设用户申请被拒绝并点击不再询问,这个方法的返回值false

这个方法的设计思想:是因为第一次申请,用户拒绝授予权限之后,以后再申请。弹框中会出现不再询问的选项。shouldShowRequestPermissionRationale方法返回true可以理解为,用户已经拒绝过一次以上了,下一次弹框会出现不再询问。所以我们发现返回值为true的时候,可能要整理一份声泪俱下的文案弹框来感动用户,让他给我们权限。尽量不要点击不再询问。然后用户被感动到之后。再弹出系统的权限申请框。
当然这个方法还可以被我们加以利用:

shouldShowRequestPermissionRationale 关键方法说明:

 * 申请权限前 false -->  申请后 true  第一次申请被拒绝
 * 申请权限前 true  -->  申请后 false 已经被拒绝过一次以上了,并且这次拒绝点击了NeverAskAgain
 * 申请权限前 true  -->   申请后 true 第二次及以上拒绝,但是未点击NeverAskAgain
 * 申请权限前 false -->  申请后 false 本次申请权限前,就已经点击过了NeverAskAgain,此时我们可能要提示用户到Setting中手动开启权限了。

3. requestPermissions和onRequestPermissionsResult两兄弟

用法类似于(startActivityForResult 和 onActivityResult),其实点开源码跟进去会发现,申请权限的底层就是用这个实现的。(回忆到之前,领导让我们每周问自己五个为什么?,第一个就是问为什么申请权限一定要在Activity中发起?最后就研究到了Context中的startActivityForResult,跑题了。)

使用方法:

//申请相机和MIC权限
requestPermissions(new String[]{ Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO}, 1);
//在回调中判断权限是否已经获取。
//因为一次可以申请多个权限。所以返回一组权限与一组对应结果。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
    List<String> deniedList = new ArrayList<>();
    for (int i = 0; i < permissions.length; i++) {
      if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
        deniedList.add(permissions[i]);
      }
    }
}

结束

Android M 的动态运行时的基础介绍就这些了。下篇会通过我写的一个组件来接管运行时权限的获取。

传送门:Android O 8.0 运行时权限适配方案

猜你喜欢

转载自blog.csdn.net/jamin0107/article/details/78924666