Android6.0+应用权限

      Android6.0([M] api 23)+之后引入了动态权限申请机制,将权限分为了正常权限和危险权限,

If your app lists normal permissions in its manifest (that is, permissions that don’t pose much risk to the user’s privacy or the device’s operation), the system automatically grants those permissions to your app.
如果应用程序列表正常的权限清单(也就是说,权限,不构成风险到用户的隐私或设备的操作),系统自动授予这些权限的应用程序。

If your app lists dangerous permissions in its manifest (that is, permissions that could potentially affect the user’s privacy or the device’s normal operation), such as the SEND_SMS permission above, the user must explicitly agree to grant those permissions.
如果你的应用列表危险的权限清单(也就是说,权限,可能会影响用户的隐私或设备的正常运行),如上述SEND_SMS许可,用户必须明确同意授予这些权限。–摘自androidDev官方文档

个人见解:
当应用使用危险权限时,不仅要在清单文件中注册,还要在运行时动态申请(run-time permission),根据用户的选择来是否授予应用危险权限。

危险权限清单

android app 设置下载文档类型权限_android

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

group:android.permission-group.CONTACTS
    permission:android.permission.WRITE_CONTACTS
    permission:android.permission.GET_ACCOUNTS    
    permission:android.permission.READ_CONTACTS
  
  group:android.permission-group.PHONE
    permission:android.permission.READ_CALL_LOG
    permission:android.permission.READ_PHONE_STATE 
    permission:android.permission.CALL_PHONE
    permission:android.permission.WRITE_CALL_LOG
    permission:android.permission.USE_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
    permission:android.permission.ACCESS_COARSE_LOCATION
  
  group:android.permission-group.STORAGE
    permission:android.permission.READ_EXTERNAL_STORAGE
    permission:android.permission.WRITE_EXTERNAL_STORAGE
  
  group:android.permission-group.MICROPHONE
    permission:android.permission.RECORD_AUDIO
  
  group:android.permission-group.SMS
    permission:android.permission.READ_SMS
    permission:android.permission.RECEIVE_WAP_PUSH
    permission:android.permission.RECEIVE_MMS
    permission:android.permission.RECEIVE_SMS
    permission:android.permission.SEND_SMS
    permission:android.permission.READ_CELL_BROADCASTS

权限检查主要方法

  • ContextCompat.checkSelfPermission(String permission)
    检查应用是否具有某个危险权限。如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED(0),并且应用可以继续操作。如果应用不具有此权限,方法将返回 PackageManager.PERMISSION_DENIED(-1),且应用必须明确向用户要求权限。
  • Activity.requestPermissions(String[] permissions, int requestCode)
    应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限。requestCode自定义用于在调用授权操作之后根据请求码进行判断。
  • ActivityCompat.shouldShowRequestPermissionRationale(Activity activity, String permission)
    如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项,此方法将返回 false。如果设备规范禁止应用具有该权限,此方法也会返回 false。
  • onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
    当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应,处理对应的场景。

Android6.0权限的检查代码

public static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // android6.0+ api23 M版本 看源码可得知
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            checkAppPermission();
        } else {
            // 23版本以下直接做你想做的
        }
    }

    public static final int REQUEST_WRITE_READ = 1;
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void checkAppPermission() {
        List<String> permissionList = new ArrayList<>();
        String[] permissions = {Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS,
            Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};
        // 一次请求多个不同组权限,弹出提示框出现两次,因为读写SD和联系人是两个不同组
        /*for (String permission : permissions) {
            if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(permission);
            }
        }
        if (permissionList.isEmpty()) {
            Log.d(TAG, "权限已授予");
        } else {
            Log.d(TAG, "request permission...");
            String[] permits = permissionList.toArray(new String[permissionList.size()]);
            requestPermissions(permits, REQUEST_WRITE_READ);
        }*/
        // 请求一个同组权限,只会弹出提示框一次,因为读写 联系人属于同组权限
        if (checkSelfPermission(permissions[0]) != PackageManager.PERMISSION_GRANTED || checkSelfPermission(permissions[1]) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS}, REQUEST_WRITE_READ);
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_WRITE_READ) {
            for (int i = 0; i < permissions.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "授权成功!");
                } else {
                    Log.d(TAG, "授权拒绝!");
                    // 如果用户之前拒绝了某项权限并且选中了权限请求对话框中的不再询问选项,
                    // 或者如果设备政策禁止该权限,该方法将返回 false。
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
                        //走到这里,说明用户之前用户禁止权限的同时,勾选了不再询问
                        //那么,你需要弹出一个dialog,提示用户需要权限,然后跳转到设置里头去打开。
                        Log.d(TAG,"用户之前勾选了不再询问...");
                        //TODO:弹出一个框框,然后提示用户说需要开启权限。
                        //TODO:用户点击确定的时候,跳转到设置里去
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", getPackageName(), null);
                        intent.setData(uri);
                        在activity结果范围的地方,再次检查是否有权限
                        startActivityForResult(intent, REQUEST_WRITE_READ);
                    } else {
                        requestPermissions(permissions, REQUEST_WRITE_READ);
                    }
                }
            }
        }
    }

总结

  1. 判断系统版本
  2. 权限的检查
  3. 若没有权限,则去申请,重写权限回调方法,对权限的授予结果进行判断;若用户拒绝了权限并且勾选了不在询问,调用shouldShowRequestPermissionRationale方法进行判断,根据需求进行操作,比如是否结束应用或者跳转到设置界面开启应用权限。
参考
Android6.0动态权限申请步骤以及需要注意的一些坑