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

一、概述

随着Android 7.0的发布,Android 6.0的普及速度很快就升上去了,目前Android 6.0的市场占有率是15.2%(具体数据可以查看Android信息中心,自从Android Developer Day大会的召开,有很多网站,我们开发者可以直接访问了,不必再爬梯子,对国内开发者来说,是很大的福音。唉,扯远了…..言归正传)这时,我们就不得不对新版本SDK中的变化做一些适配,这样才能保证应用更好的运行。对于6.0中的变化,我们可以参考官网的这篇文章:Android 6.0变更。该篇文章主要对Android 6.0 运行时权限(Runtime Permissions)做一下介绍。

这里推荐官网的两篇文章,毕竟官方的文档才是最科学的:

  • 使用系统权限
  • 权限最佳做法

二、运行时权限

从Android6.0(API级别23)开始,用户开始在应用运行时向其授权,而不是在应用安装时授权。此方法可以简化应用安装过程,同时用户可以对应用的功能进行更多的控制。对于6.0以下的,当我们安装应用时默认就授权所有的权限了,用户也不了解这些权限到底有什么用,只能默默忍受。。而新的权限机制可以很好的解决这一系列问题。Google将新的权限分为正常权限危险权限

  • 正常权限:正常权限涵盖应用需要访问起沙盒外部数据或资源,但对于用户隐私或其它应用操作风险很小的区域。例如,设置时区的权限就是正常权限。如果应用声明气需要正常的权限,系统会自动向应用授予该权限。这里可以参考官网的正常权限的列表。

  • 危险权限:危险权限涵盖应用需要涉及用户隐私信息的数据活资源,或者可能对用户存储的数据活其它应用的操作产生影响的区域。例如,读取用户的联系人就属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。其实我们在开发中,只要处理好危险权限,正常权限的处理方式和之前一样。下面贴出危险权限图:

我们看上面的危险权限,会发现危险权限是分组的,那么分组会对我们的权限有影响吗?的确是有影响的。如果你的APP运行在Android 6.0以上的机器上(targetSdkVersion >= 23下面会细说),授权机制是这样的。如果你申请某个危险权限,假设你的App早已被用户授予了同一组中的某个危险权限,那么系统会立即授权,则不会弹出对话框让用户去授权。例如,你的App已经对CONTACTS权限组中的READ_CONTACTS授权了,当你的App申请WRITE_CONTACTS权限时,系统则会直接授权通过。此外,对于申请时弹出Dialog的文本说明也是对整个权限组的说明,而不是单个权限。这里需要注意的是:弹出的Dialog是系统提供,我们是不能进行定制的。

三、权限适配

首先我们按照之前的方式来申请拨打电话的权限(拨打电话权限),在Android 6.0(targetSdkVersion >= 23)手机上进行测试。

//首先在清单文件中申请拨打电话的权限//在Button的点击事件中,使用Intent拨打电话Intent intent = new Intent(Intent.ACTION_CALL);intent.setData(Uri.parse("tel:" + phoneNumber));startActivity(intent);      //此行代码会报红线。(android studio 2.2.2版本)看来AS还是挺人性化的。

运行App,点击拨打电话按钮,你会发现App崩溃了。。。下面贴出异常原因图:

从图中可以很清楚的看到是因为SecurityException权限异常。解决这个异常有两种方法:

  1. 在android studio中,打开build.gradle(module:app)文件,将targetSdkVersion的版本号修改为低于23的,即可解决该异常。那就继续使用旧有规则:用户在安装的时候不得不接受所有权限,安装后app就有了那些权限咯!
  2. 使用Android提供的相关API进行权限的检查,避免这个异常。

但是作为一个有“情怀”的程序猿,我们怎么可能用第一种这么low的方法去解决问题呢。下面我们使用Android提供的相关API来处理异常。

  1. 首先在清单文件中申请拨打电话的权限,这一步是必不可少的。

  2. 在Button的点击事件,拨打电话前,首先使用ActivityCompat.checkSelfPermission()方法检查是否有拨打电话权限(ActivityCompat和ContextCompat是子父类的关系),该方法有两个int类型的返回值:分别为PERMISSION_GRANTED(表示应用有此权限)和PERMISSION_DENIED(表示应用没有权限),如果此时返回值为PERMISSION_DENIED,那么我们就应该手动去请求应用的权限,看代码。

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {    /**     * 请求拨打电话权限     * 该方法是异步的,第一个参数是Context;     * 第二个参数是需要申请的权限的字符串数组;     * 第三个参数为requestCode,主要用于回调的时候检测。     * 可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。     */    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);} else {    //有权限,直接调用拨打电话的方法    mLoginPresenter.call(this);}
  3. 在Activity中重写onRequestPermissionsResult方法,处理请求权限的回调。首先验证requestCode定位到你的申请,然后验grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了。

    @Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    super.onRequestPermissionsResult(requestCode, permissions, grantResults);    switch (requestCode) {        case 1:            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {                mLoginPresenter.call(this);            } else {                Toast.makeText(this, "未授权拨打电话权限", Toast.LENGTH_LONG).show();            }            break;    }}

申请权限的基本步骤就如上所示,没图没真相。接下来我们就来看下真相吧。上图。。。

如果用户拒绝某授权。下一次弹框,用户会有一个“不再提醒”的选项的来防止app以后继续请求授权。如果这个选项在拒绝授权前被用户勾选了,那么下次你再点击拨打电话时,Dialog将不会在提示,App什么也不干,这对用户来说是很差的体验。后文会说处理的方法。

注意:不同手机上,可能提示的方式不同,下面看下下米手机上的提示。(小米4手机上即使你拒绝很多次,它的那个Dialog上也不会出现“不在询问”的勾选框),可能是国内的手机厂商对Rom做了处理。

四、更优雅的处理权限提示问题

如果用户拒绝某授权。下一次弹框,用户会有一个“不再提醒”的选项的来防止app以后继续请求授权。如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,结果就是,app啥都不干。这将是很差的用户体验,用户做了操作却得不到响应。这种情况需要好好处理一下。在请求requestPermissions前,我们需要检查是否需要展示请求权限的提示通过activity的shouldShowRequestPermissionRationale方法,如果该方法返回true,则表示用户已经拒绝过一次权限,此时我们应该弹一个消息提示框,表明请求该权限的原因,让用户授权该权限。代码如下:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {            showSecurityMessage("是否授权拨打电话权限,若未授权,则不能拨打电话。", new DialogInterface.OnClickListener() {                @Override                public void onClick(DialogInterface dialog, int which) {                    ActivityCompat.requestPermissions(LoginActivity.this,                            new String[]{Manifest.permission.CALL_PHONE},                            1);                }            });            return;        }        /**         * 请求拨打电话权限         * 该方法是异步的,第一个参数是Context;         * 第二个参数是需要申请的权限的字符串数组;         * 第三个参数为requestCode,主要用于回调的时候检测。         * 可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。         */        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);    } else {        //有权限,直接拨打        mLoginPresenter.call(this);    }   private void showSecurityMessage(String message, DialogInterface.OnClickListener okListener) {    new AlertDialog.Builder(this)            .setMessage(message)            .setPositiveButton("是", okListener)            .setNegativeButton("否", null)            .create()            .show();}

注:当你一次请求多个权限时,不要忘了为没个权限添加解释说明。

效果图:

五、使用兼容库兼容旧版本

以上代码在android 6.0以上运行没有问题,但是API 23之前的就不行了,因为没有那些方法。粗暴的方法就是检查版本:

if (Build.VERSION.SDK_INT >= 23) {    // Marshmallow+} else {    // Pre-Marshmallow}

但是太复杂,这里我们可以使用v4兼容库,已对这个做过兼容,用以下函数代替:

  • ContextCompat.checkSelfPermission() 被授权函数返回PERMISSION_GRANTED,否则返回PERMISSION_DENIED ,在所有版本都是如此。
  • ActivityCompat.requestPermissions() 这个方法在M之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED结束 。
  • ActivityCompat.shouldShowRequestPermissionRationale() 如果此函数在M之前调用,它将永远返回false。

用v4包的这三方法,完美兼容所有版本!这个方法需要额外的参数,Context or Activity。其它的就没什么特别的了。上面的后两个方法,我们也可以在Fragment中使用,用v13兼容包:FragmentCompat.requestPermissions() and FragmentCompat.shouldShowRequestPermissionRationale()和activity效果一样。

六、使用三方开源库

以上代码在实际开发中写着还是很麻烦的,只有申请的权限是危险权限,那么就要去检查。当然,你也可以自己去封装下,方便自己使用。下面是github上star数最多的关于Permissions库,大家在开发中可以直接使用。

  • RxPermissions
  • easypermissions

参考文章:https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en

更多相关文章

  1. Android ListView的item点击无响应的解决方法
  2. Android-sharedUserId数据权限
  3. Android调用JNI出错 java.lang.UnsatisfiedLinkError: No implem
  4. android中使用jni,ndk的C语言回调方法
  5. 通过Android Studio3.5.3编译安装App失败,提示安装包异常解决方
  6. Android 6.0 运行时权限 处理

随机推荐

  1. Android系统中prop详解
  2. android内部外部存储卡路径的获取
  3. .Android(安卓)Notification 基础
  4. Android Touch事件传递机制
  5. android jni 输出log
  6. Android之使用picker打开相应的app
  7. Android Web Server
  8. 第11章(1)---Android的线程和线程池
  9. Android改变文件的权限
  10. 了解 uri, content provide,包括 data and