Android开发——Android 6.0权限管理机制详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SEU_Calvin/article/details/52163456

0.前言

最近在研究所实习,我负责维护Android手机取证项目的Android客户端,有客户反映我们的APPAndroid6.0无响应,经过调试发现SD卡读写权限权限被拒绝。但明明是在AndroidManifest.xml文件中声明过的。查了很多资料才知道Android6.0的很多权限申请机制发生了改变,可以说是Android6.0在安全机制上更进了一步吧,因此写下这篇文章以记录。

PS:在我们的产品中,因为我们确信客户在使用时不会在权限申请时点“拒绝”,因此我对此功能的实现也仅限于文章中的前部分,也就是直接做死循环直到用户同意授权,因为当然是用最低的成本满足客户的需求最好啦,但是真正开发中,需要处理更多的事情,我也并没有浅尝辄止,后面会用到onRequestPermissionsResult回调方法,shouldShowRequestPermissionRationale方法等,才能避免很多令用户困扰的情况。后面会详细地进行介绍。

我也花了整整两天的时间对兼容Android6.0权限管理机制的整个处理过程进行了理解和汇总,也方便大家遇到类似的问题少走弯路。本文原创,转载请标明出处:http://blog.csdn.net/seu_calvin/article/details/52163456


1Android 6.0新的权限管理机制

Android 6.0 Marshmallow版本之后,对于一些危险级别的权限,运行在targetSdkVersion设置为2323以上的应用、并且代码逻辑需要这些权限时,需要我们在代码中实现实时的询问用户是否授予权限若不询问直接进行有关这些权限的代码逻辑,会出现类似java.lang.SecurityException: Permission Denial的异常日志。

targetSdkVersion如果设置为23以下,系统还是会使用旧的权限管理规则但是6.0以后,用户可以在<设置-权限>里将该APP的某些权限手动关闭,此时被用户禁止权限的API接口返回值都为null或者0,我们判空即可防止App Crash

若APP在运行时,将设置里的权限手动关闭,那就会直接Crash。

 

2.危险权限列表

前面提到的危险级别的权限是我们需要格外关注的,因为这些权限在使用前需要进行特殊处理。

 

上图中有一个权限群的概念,同一组的任何一个权限被授权了,其他权限也自动被授权。

例如,一旦WRITE_EXTERNAL_STORAGE被授权了,APP同时也就有了READ_EXTERNAL_STORAGE权限。

 

3Android 6.0运行时主动请求权限

3.1  检测和申请权限

下面的例子介绍上面列出的读写SD卡的使用例子,可以使用以下的方式解决:

public boolean isGrantExternalRW(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && activity.checkSelfPermission(
            Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

        activity.requestPermissions(new String[]{
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        }, 1);
        return false;//第一次开启应用并执行权限检查,虽然返回了false,但是已经调用过了申请权限的方法
    }
    return true;//非第一次开启应用并执行权限检查,或者6.0以下的Android版本
}

检查和申请权限的方法分别是Activity.checkSelfPermission()和Activity.requestPermissions,这两个方法是在 API 23中新增的。

Activity.checkSelfPermission()主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了。

Activity.requestPermissions该方法是异步的,第一个参数需要申请的权限的字符串数组,第二个参数为requestCode,主要用于回调的时候检测。最前面的参数可以传入mActivity。好像不传也可以。

可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。


然后在需要使用这个权限的时机,进行如下调用即可。

 boolean isGrant= isGrantExternalRW(mActivity);
 if (isGrant) {
      //业务逻辑
 }
 while (!isGrant) {
      try {
         Thread.sleep(1000);
} catch (InterruptedException e) {
        e.printStackTrace();
}
    isGrant = isGrantExternalRW(mActivity);
    if (isGrant) {
       //业务逻辑
   }
}

这里因为我们写的isGrantExternalRW方法的返回值只是判断的一开始检查权限时的状态,因此如果是第一次开启应用,返回的是false,并且申请了权限(如果用户同意的话),再调用一次该方法返回true,进入逻辑代码,并最后跳出while循环。如果用户已经授权过了,那么直接会走逻辑代码。这里比较流氓的是,如果用户一直不同意,会一直返回false,我们就阻塞在while循环里,一秒后继续申请,直到用户同意为止。显然这是不够友好的。

因此Google为了防止这种情况的发生,在用户拒绝授权时,下一次弹窗可以勾选“不再提醒”如果这个选项被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,系统会直接回调处理申请返回结果的回调方法onRequestPermissionsResult,回调结果为最后一次用户的选择。


3.2 处理权限申请回调

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case 1: {
            // 用户取消授权这个数组为空,如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //业务逻辑
            } else {
                //授权被拒绝,不再进行基于该权限的功能
            }
            return;
        }
        // 其他case处理其他权限的申请回调
    }
}

上面处理申请回调的方法已经写的很明白了。

还有就是不管用户点击拒绝还是同意,都会回调该方法(别忘记用户拒绝并勾选“不再提醒”时也会回调)。


3.3  使用shouldShowRequestPermissionRationale方法

问题来了,如果第二次向用户申请权限被拒绝,并且用户勾选了“不再提醒”,那我们以后每次需要使用这个权限都会直接被拒绝,并不会弹出对话框。APP什么也不做会产生很差的用户体验。所以这种情况需要我们进行处理。这时候我们可以借助shouldShowRequestPermissionRationale方法。首先看一下该方法的返回值。


因此,我们在回调函数中做权限检测,如果返回DENIED,就调用上述方法,返回false,就弹出对话框引导用户手动开启权限,避免了用户的操作触发了权限申请机制(已被拒绝并勾选不再提醒或手动关闭权限),但是没有任何响应的尴尬。又因为我们事先向用户询问授权过了,因此不存在表格中的第一种情况。

因此只需要在上面回调代码的else代码段加入如下代码即可。

                if (!mActivity.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    //用户已经完全拒绝,或手动关闭了权限
                    //开启此对话框缓解一下尴尬...
                    AlertDialog dialog = new AlertDialog.Builder(this)
                            .setMessage("不开启该权限将无法正常工作,请在设置中手动开启!")
                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    finish();
                                }
                            })
                            .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    finish();
                                }
                            }).create();
                    dialog.show();
                    return;
                }else{
                    //用户一直拒绝并一直不勾选“不再提醒”
                    //不执行该权限对应功能模块,也不用提示,因为下次需要权限还会弹出对话框
                }

在需要权限的时候,只要执行类似于下面的检查和申请权限的代码即可(注意自己替换ContentCompat):

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M 
&& ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) 
!= PackageManager.PERMISSION_GRANTED) {     
ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } 
else {     //Do the stuff that requires permission... 
}


转载请标明出处:http://blog.csdn.net/seu_calvin/article/details/52163456


猜你喜欢

转载自blog.csdn.net/SEU_Calvin/article/details/52163456