自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有的权限而导致的无法安装的事情,也不会再不征求用户授权的情况下,就可以任意的访问用户隐私,而且即使在授权之后也可以及时的更改权限。这就是6.0版本做出的更拥护和注重用户的一大体现。
1 andriod6.0权限
andriod6.0系统把权限分为两个级别:
一个是Normal Permissions,即普通权限,这类权限不会潜藏有侵害用户隐私和安全的问题,比如,访问网络的权限,访问WIFI的权限等;
另一类是Dangerous Permissions,即危险权限,这类权限会直接的威胁到用户的安全和隐私问题,比如说访问短信,相册等权限。
具体普通权限和危险权限参考:https://www.cnblogs.com/wwjldm/p/6932083.html
权限组:普通权限是单条的权限,而危险权限是以组展示的。所有危险的权限都属于权限组。也就是说,当你接受一个危险权限时,它所在的这个组里面的其他所有访问权限也将会被自动获取权限。当然,这类危险权限也是需要在manifest中注册的,否则动态申请会失败。
实际测试:在manifest中添加读取短信权限,属于危险权限,在没有做相应处理时,观察是否起作用。
targetSdkVersion>=23 ,真机>=23 ,出现权限异常
targetSdkVersion<23 ,真机>=23 ,正常访问
targetSdkVersion<23 ,真机<23 ,正常访问
targetSdkVersion>=23 ,真机<23 ,正常访问
结论:当真机系统版本小于6.0时,权限在manifest声明了,就可以正常访问。当真机系统版本不小于6.0且targetSdkVersion>=23时,需要动态申请权限。手机不断的在升级,版本会越来越高,应用不可能一直只支持低版本(6.0以下)而不支持高版本,所以APP权限升级是必然的。
2 android 8.0 权限策略变化
在 Android 8.0 (targetSdkVersion 26)之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE,并且用户授予了该权限。
如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。
如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。如果不申请,会抛异常。
小结:
- 以前,申请一个子权限会自动获取权限组中其他子权限。组内其他子权限可以直接使用。
- 现在,申请一个子权限,组内其他子权限不会自动获取。使用组内其他子权限的时候。需要再次申请。(但是这种情况不会弹出系统的权限申请框)如果不申请。会FC。
- 同组权限一起申请。当我们申请权限时。申请同组的多个权限时,也只会弹出一次申请框。所以建议一起申请。
以下原因不会弹框(不仅包括)
- 6.0以下版本(系统自动申请)
- 暂时发现vivo、oppo、魅族的6.0以上版本
因为这些厂商修改了6.0系统申请机制,他们修改成系统自动申请权限了。也就是说这些系统会跟以前 6.0 以下的版本一样,需要用到权限的时候系统会自动申请,就算我们主动申请也是没用的
3 动态申请权限
使用步骤
1 清单配置需要的权限
2 检查版本是否大于M
3 检查是否拥有申请的权限
4 没有,去申请
5 覆盖 onRequestPermissionsResult方法,根据requestCode,permissions,grantResults判断我们是否申请成功
6 拒绝的时候利用 ActivityCompat.shouldShowRequestPermissionRationale判断是否勾选'不再询问'
7 如果勾选'不再询问',下次再申请此权限时默认返回拒绝。需自定义dialog,让用户去设置里面开启权限。
public final class PermissionUtil { /** * 危险权限组 **/ public static final String[] CALENDAR; // 读写日历 public static final String[] CAMERA; // 相机 public static final String[] CONTACTS; // 读写联系人 public static final String[] LOCATION; // 读位置信息 public static final String[] MICROPHONE; // 使用麦克风 public static final String[] PHONE; // 读电话状态、打电话、读写电话记录 public static final String[] SENSORS; // 传感器 public static final String[] SMS; // 读写短信、收发短信 public static final String[] STORAGE; // 读写存储卡 public static final int REQUEST_STORAGE = 1000; public static final int REQUEST_CAMERA = 1001; public static final int REQUEST_CONTACTS = 1002; public static final int REQUEST_LOCATION = 1003; public static final int REQUEST_MICROPHONE = 1004; public static final int REQUEST_PHONE = 1005; static { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {//当前版本小于6.0 CALENDAR = new String[]{}; CAMERA = new String[]{}; CONTACTS = new String[]{}; LOCATION = new String[]{}; MICROPHONE = new String[]{}; PHONE = new String[]{}; SENSORS = new String[]{}; SMS = new String[]{}; STORAGE = new String[]{}; } else { CALENDAR = new String[]{ Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR}; CAMERA = new String[]{ Manifest.permission.CAMERA}; CONTACTS = new String[]{ Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.GET_ACCOUNTS}; LOCATION = new String[]{ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}; MICROPHONE = new String[]{ Manifest.permission.RECORD_AUDIO}; PHONE = new String[]{ Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE, Manifest.permission.READ_CALL_LOG, Manifest.permission.WRITE_CALL_LOG, Manifest.permission.ADD_VOICEMAIL, Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS}; SENSORS = new String[]{ Manifest.permission.BODY_SENSORS}; SMS = new String[]{ Manifest.permission.SEND_SMS, Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_WAP_PUSH, Manifest.permission.RECEIVE_MMS}; STORAGE = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; } } public static boolean hasPermission(Context context, String[] permissions) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; for (String permission : permissions) { return hasPermission(context, permission); } return true; } public static boolean hasPermission(Context context, String permission) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) return false; return true; } public static void requestPermission(Activity activity, String[] permissions, int request) { ActivityCompat.requestPermissions(activity, permissions, request); } }
private void checkStoragePermission() { if (!PermissionUtil.hasPermission(this, PermissionUtil.STORAGE)) { PermissionUtil.requestPermission(this, PermissionUtil.STORAGE, PermissionUtil.REQUEST_STORAGE); } else { //granted .. to do something } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case PermissionUtil.REQUEST_STORAGE: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //granted.. to do something } else { //shouldShowRequestPermissionRationale 拒绝时是否勾选了不再提示 // 没有勾选返回true ,勾选不再提醒返回false boolean hasAlwaysDeniedPermission = !ActivityCompat.shouldShowRequestPermissionRationale(this, PermissionUtil.STORAGE[0]); //勾选不再提醒,会过滤系统的对话框,这时需要手动去设置权限 if (hasAlwaysDeniedPermission) { handlePermission(); } } break; default: break; } } private void handlePermission() { final CustomDialog dialog = new CustomDialog(this); dialog.setTitle("提示"); dialog.addPositiveListener("去添加权限", new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivityForResult(intent, PermissionUtil.REQUEST_STORAGE); dialog.dismiss(); } }); dialog.addNegativeListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); } }); TextView textView = new TextView(this); textView.setText("应用需要访问读取权限,不授权将无法正常工作!"); dialog.addChildView(textView); dialog.show(); }