关于写这个的原因

动态权限已经是老生常谈的东西了,也做过很多次了,但是由于出现的频率比较低,大概的流程是清楚了,但是有些 API 就是记不住,现在有空便整理一下,加深印象。

为什么需要动态权限

Android 从 6.0 开始,部分高危权限的申请需要使用动态权限,否则程序会直接报错,这个是在 targetVersion > 23 或者运行设备安卓版本大于 6.0 的情况下可能会发生。

其实动态权限这个东西,早已经就不是什么新鲜事情了。在之前 MIUI 5 吧,就已经有这个东西了,用户在需要使用到这个权限的时候,才会弹出选择框,向用户申请权限。后面 Google 就在 Android 6.0 的版本做了强制要求,高危权限必须要使用 动态权限。

哪些是高危权限

要说哪些是高危权限,其实我们大概想想就知道。

比如,打电话?读取联系人?拍照? 是啊,要是程序不需要授权就可以使用前面的功能那就太恐怖了。悄无声息的打电话,发短信,拍照,那用户就相当于在程序面前赤身裸体,他们都被看光了。在这个时代这种事情是不会允许发生的。然而动态权限这个东西还是在 6.0 才被 Google 加进来的。。。。。。

好了,说正事。其实这些权限都是分组的,只要这组中的一个权限被授予了,那这一组中其他权限也会被授予。好了,我们来看一下到底有哪些权限需要动态申请呢?

  • CALENDAR(日历)

    • READ_CALENDAR
    • WRITE_CALENDAR
  • CAMERA(相机)

    • CAMERA
  • CONTACTS(联系人)

    • READ_CONTACTS
    • WRITE_CONTACTS
    • GET_ACCOUNTS
  • LOCATION(位置)

    • ACCESS_FINE_LOCATION
    • ACCESS_COARSE_LOCATION
  • MICROPHONE(麦克风)

    • RECORD_AUDIO
  • PHONE(手机)

    • READ_PHONE_STATE
    • CALL_PHONE
    • READ_CALL_LOG
    • WRITE_CALL_LOG
    • ADD_VOICEMAIL
    • USE_SIP
    • PROCESS_OUTGOING_CALLS
  • SENSORS(传感器)

    • BODY_SENSORS
  • SMS(短信)

    • SEND_SMS
    • RECEIVE_SMS
    • READ_SMS
    • RECEIVE_WAP_PUSH
    • RECEIVE_MMS
  • STORAGE(存储卡)

    • READ_EXTERNAL_STORAGE
    • WRITE_EXTERNAL_STORAGE

其实一般用的比较多的就是 存储卡呀,电话呀,手机呀,短信呀,位置呀,联系人呀,拍照呀,这些而已。好像除了 麦克风,传感器,日历之外,其他的都挺常用的。。。。。

补充一点,如果在 6.0 以下的手机使用动态权限,会直接返回拒绝,连个弹窗都没有。所以在使用动态权限的时候一定要先做版本判断。

动态申请权限的步骤

  1. 在 Androidmanifest.xml 文件中注册权限。
  2. 检查有没有被授予权限。
  3. 该权限是否被拒绝过,如果被拒绝过,最好提示一下用户再进行申请。
  4. 申请权限。
  5. 权限是否被授予。
  6. 如果没被授予,提示用户,如果被勾上“不再提示”,该如何处理。

检查是否被授予权限

ContextCompat.checkSelfPermission(context, permission);

就是这个逼,我一直记得后面的方法名,但是就是忘记了是哪一个类。 ContextCompat,ContextCompat,ContextCompat 默念三遍。其实这个类是 Suport v4 包里面的,用这个也是为了兼容处理,在高版本的 SDK 中,是有可以直接检查权限的方法,但是为了兼容,我们还是用这个吧。

这个检查权限的方法会返回一个 int 类型的数据,我们可以根据这个返回值判断是否被授予了 权限。

在 PackManager 中有两个常量,就是用来做这个判断的。分别是:

PackManager.PERMISSION_DENIED : 权限被拒绝
PackManager.PERMISSION_GRANTED : 权限被授权

我们只要跟这两个常量作比较就可以了。

申请权限

ActivityCompat.requestPermissions(activity,permissions[],requestCode)

这个也是Support v4兼容包里面的。调用这个就会发送一个权限申请请求。前提是没有被用户勾选为“不再提示”。我们申请权限的时候可以几个权限一起申请。

权限是否被授予

onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)

这个方法有点像onActivityResult。我们申请权限的时候,弹出权限申请窗口,用户操作后,结果会在这个方法中回调。因为申请时候可以是几个权限一起申请,所以回调的时候也是一起回调的。grantResults 中保存着所有的结果,同样的,我们只要跟 PackManager 中的两个权限相关的常量进行比较就可以了。

权限是否被拒绝过

ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)

同样的,这个类也是 Support v4 包中的类,高版本中也是有可以直接用的,但是我们为了兼容还是使用兼容包。我们调用这个方法的时候,如果返回是 true ,则说明,上次用户拒绝过我们的权限申请。

处理 “不再提示” 的情况

我们还可以使用这个方法来判断用户是不是勾选了“不再提示”的选项。处理过程是这样子的。

我们先判断有没有权限,如果没有这个权限就去申请,如果申请的结果是 失败 的话,我们再调用这个方法,如果此时返回的是 false 那就有蹊跷了,用户不是拒绝了我们的权限申请了吗?怎么返回了 false? 这个时候就是用户勾选了 “不再提示” , 因为用户直接就不给你机会了,这个时候我们再去申请权限都是没有用了,浪子不会回头的,我们只能提示用户爸爸,让爸爸去设置里面把权限授权给我们了。

代码

下面附上一个写好的代码,及供参考

    private String[] requestPermission = new String[]{Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_EXTERNAL_STORAGE};

    private final int permissionRequestCode = 10010;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Android 版本的大于 6.0 才使用动态权限
        if (Build.VERSION.SDK_INT>Build.VERSION_CODES.M){
            final List<String> needRequestPermission = checkPermissions(this, requestPermission);
            if (isShouldShowReason(this,needRequestPermission)){
                // 之前被拒绝过,现在要提示用户
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("申请权限")
                        .setMessage("申请这个权限是为了 XXXX ,请授予这个权限")
                        .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions(needRequestPermission,MainActivity.this, permissionRequestCode);
                            }
                        })
                        .setNegativeButton("滚",null)
                        .show();
            } else if (needRequestPermission.size()>0){
                requestPermissions(needRequestPermission,this, permissionRequestCode);
            }
        }

    }
    //检查权限
    private List<String> checkPermissions(Context context , String[] requestPermission){
        List<String> needRequest = new ArrayList<>();
        for (String permission : requestPermission){
            int result = ContextCompat.checkSelfPermission(context, permission);
            if (result!=PackageManager.PERMISSION_GRANTED){
                needRequest.add(permission);
            }
        }
        return needRequest;
    }
    //是否需要展示申请权限的原因
    private boolean isShouldShowReason(Activity activity,List<String> permissions){
        boolean shouldShowReason=false;
        for (String permission: permissions){
            shouldShowReason = ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
            if (shouldShowReason) {
                break;
            }
        }
        return shouldShowReason;
    }
    // 申请权限
    private void requestPermissions(List<String> permissions, Activity activity, int requestCode){
        ActivityCompat.requestPermissions(activity,permissions.toArray(new String[]{}),requestCode);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if(requestCode== permissionRequestCode){
            boolean requsetPermissionResult=true;
            boolean needSetting = false;
            for (int i=0;i<grantResults.length;i++){
                int result = grantResults[i];
                if (result!=PackageManager.PERMISSION_GRANTED){
                    requsetPermissionResult=false;
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[i])){
                        //用户选择了不再提示。
                        needSetting=true;
                    }
                }
            }
            if (requsetPermissionResult){
                Toast.makeText(this, "获取权限成功!", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(this, "获取权限失败!", Toast.LENGTH_SHORT).show();
            }
            if (needSetting){
                // 用户勾选了 “不再提示”
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("申请权限")
                        .setMessage("申请这个权限是为了 XXXX ,请去设置中添加权限")
                        .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .setNegativeButton("滚",null)
                        .show();
            }
        }
    }