Android 6.0的RuntimePermission

前言

因为工作的情况,需要解决app在Android 6.0的一些权限问题,所以就此好好研究了一下。

新的运行时权限

Android的权限系统在6.0之前一直都是用户在安装时向用户请求授权的,也因此app在安装后就有可能在用户不知情的情况下获取一些隐私信息。
为了解决这个问题,Google在Android 6.0改进了权限系统,将一些**涉及到用户隐私的权限改为app运行时动态地向用户请求授权 **。这样子不仅可以有效保护用户的隐私,同时也简化了app的安装过程,可谓一举两得。

Android 6.0的RuntimePermission_第1张图片
图片.png

但是对于我们这些开发者而言,就比较麻烦了。因为上图那个提示Dialong是不会自动弹出来的,需要我们去调用相应的代码的。如果我们需要摄像头的权限而没有调用代码去动态获取权限的话:

Android 6.0的RuntimePermission_第2张图片
图片.png

对于不涉及用户隐私的权限我们还是和以前一样只需要在清单文件里添加就可以了,动态申请权限仅在设计用户隐私的权限才需要。那么那些权限涉及到用户的隐私呢?下面是在Android官网找到的:

Android 6.0的RuntimePermission_第3张图片
图片.png

Google将涉及用户隐私的权限分为9个权限组,每组只要有一个权限申请成功了就默认整个权限组的权限都申请成功了。

动态申请权限的步骤

要动态申请权限,首先app的targetSdkVersion就要设置为>=23,接着在清单文件里添加你需要申请的隐私权限:

Android 6.0的RuntimePermission_第4张图片
776.png
//先用摄像头来举例

上面两步解决了,现在就可以在代码里面动态申请权限了。这里为了兼容低版本而是用了ActivityCompat这个工具类。

  1. 检查app是否得到了用户授予的隐私权限
    public void click(View view) {
         //检查是否授予了摄像头的权限
         int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
         //如果拥有该权限,permission值此时为PackageManager.PERMISSION_GRANTED(granted翻译过来就是准许)
         //如果没有这个权限,permission值为PackageManager.PERMISSION_DENIED(denied翻译过来就是拒签)
         if (permission != PackageManager.PERMISSION_GRANTED) {
             //没有授予摄像头的权限,那么就去动态地向用户申请该权限
             requestCameraPermission();
         } else {
             //用户已经授权了该权限就直接启动摄像头
             openCamera();
         }
     }
    

..........
//启动系统相机拍照
private void openCamera() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivity(intent);
}

2. 向用户说明需要授权什么权限,请用户确认

private static final int REQUEST_CAMERA = 0;

private void requestCameraPermission() {
//requestPermissions弹出一个系统的Dialog,说明当前要申请什么权限
//requestPermissions需要一个String数组和一个请求码用于回调
//String数组可以填写多个你需要申请的权限
//需要一个请求码用于处理申请权限的结果
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA);
}


3. 重写onRequestPermissionsResult()方法来处理向用户动态申请权限的结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    //依据请求码来做处理
    if (requestCode == REQUEST_CAMERA) {
        //检查申请的权限是否准许了
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //用户授权了摄像头的权限,这里就可以做我们想做的事情了.
            openCamera();
        } else {
            Toast.makeText(MainActivity.this, "用户拒绝授权摄像机的权限", Toast.LENGTH_SHORT).show();
        }
    }
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
这样子搞定了。细节什么的,我想我的注释已经写得很清楚了。

####处理不再询问的情况
当我们弄好动态申请权限的调用,第一次弹出系统Dialog是一开始那幅图的样子:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-326448e880b56d2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果用户点了拒绝的话,第二次进入就会多了一次提示:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-7ec490bd6f911c2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果用户点击了不再询问并拒绝了授权,那么用户下次想给app授权时就什么都干不了,因为这个系统Dialog直接就不弹出来了。
查看官方文档的话,会看到官方有提供一个shouldShowRequestPermissionRationale()方法来判断用户是否曾经拒绝过授权。但是我们看下官方文档的下面这个说明:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-744299427cd75d00.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

用户拒绝过app一次授权的话,这个方法将会返回true。但是**如果用户是点击了不再询问并拒绝授权或是手机设备直接禁止授权(比方说在setting的应用页面里面禁止了某个权限)的话,这个方法都是直接返回false的**。
换言之,我们根本没有必要(目前也没有方法)追踪用户是否点击了不再询问。目前还是有一个比较好的处理方法的,就是在用户拒绝授权后提示用户到手机设置里面打开该权限:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    //依据请求码来做处理
    if (requestCode == REQUEST_CAMERA) {
        //检查申请的权限是否标准了
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
           //..............
        } else {
            new AlertDialog.Builder(MainActivity.this)
             .setMessage("你已经拒绝了授权摄像头的权限,可以到手机设置->应用管理->permission->权限里面来进行手动授权才能开启摄像头的功能。\n" +
                            "也可以点击ok按键直接跳到应用权限设置的页面" )
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            openAppInfo();
                        }
                    })
                    .setNegativeButton("Cancel", null)
        }

    }
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

private void openAppInfo() {
    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", getPackageName(), null);
    intent.setData(uri);
    startActivity(intent);
}
那么用户拒绝授权后就是下面这个样子:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-84947b87d082981a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这样子用户看到提示后就可以选择按下ok去到应用信息页面来手动设置权限:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-09cfabcea350d231.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这样子就可以处理国产手机以外的情况。是的,上面的做法对于**国产手机是无效的**。因为**国产手机(小米魅族之类的)都有自己定义的一套权限管理系统**。
一旦你拒绝了授权,那么你就只能点击手机的安全中->权限管理->应用权限管理->自己的app里面来手动打开权限才行。哪怕你已经在setting的应用信息页面里面手动授权了,只要国产手机的安全中心那里没有打开授权,那你的app还是无法得到授权的。

![小米魅族之类的国产手机一旦拒绝了授权只有在安全中心这里手动授权了才可以使用摄像头.png](http://upload-images.jianshu.io/upload_images/1445840-96e4611184c94b53.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

####结语
目前市面上的Android6.0手机已经越来越多了,权限适配是每个Android开发者都避免不了的,尤其是国内的开发者,国产手机自己另开炉灶来搞自己那一套权限系统,可以说让我们非常头疼。
如果觉得上面这么一套权限适配的代码写起来很麻烦,也有第三方库来帮助我们来简化代码,这里推荐两个:
[easypermissions,这个是Google推出的](https://github.com/googlesamples/easypermissions)
[PermissionsDispatcher,这个在GitHub上面的star非常多](https://github.com/googlesamples/easypermissions)

你可能感兴趣的:(Android 6.0的RuntimePermission)