运行时权限检查

概括

简单粗暴的来说一下,在 Android 6.0 即 API 23 之前,我们用到的权限只需要在 AndroidManifest 文件中统一进行申请就可以了,而且用户不可取消!

但是在 6.0 及以上,出于安全性考虑,开始由用户掌握了部分主动权!

Android 中的系统权限可以分为 normal 和 dangerous 两种。

normal 这类的权限不太会直接威胁到用户的隐私,只需要在 Manifest 文件中注册即可;

dnagerous 这类的权限使得 app 可以直接访问用户的一些隐私数据,从 Android 6.0 开始,这些权限就不仅仅只需要在 Manifest 文件中声明,而且需要在运行且适当的时候进行动态的申请,由用户决定是否授权,并且用户可以随时取消这些权限(在设置里的应用详细信息界面取消)。如果不进行权限检查而贸然执行一些“危险”的动作的话,应用可能是会崩溃的。

Dangerous Permissions List

这里只给出 dangerous 类的权限列表:

权限组 组内权限
CALENDAR READ_CALENDAR

WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS

WRITE_CONTACTS

GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION

ACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE

CALL_PHONE

READ_CALL_LOG

WRITE_CALL_LOG

ADD_VOICEMAIL

USE_SIP

PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS

RECEIVE_SMS

READ_SMS

RECEIVE_WAP_PUSH

RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE

WRITE_EXTERNAL_STORAGE

如上表,权限被分组了,值得一提的是 同一组中的任何一个权限被授权了,其他权限也就自动被授权了

最基本的使用步骤

申请授权一般需要三步:

  1. 检查权限是否已经授权
  2. 申请用户授权
  3. 处理授权结果

好在 Android 提供了相应的 API 使得我们开发便利许多,下面以申请 “读取联系人权限” 为例展开这三个步骤。

第一步,检查权限是否已经授权

//读取联系人的权限String permission = Manifest.permission.READ_CONTACTS;//检查权限是否已授权int hasPermission = checkSelfPermission(permission);

只需要调用 checkSelfPermission(String permission) 方法,参数为 权限字符串。

如果是已授权的权限,该方法返回结果是 PackageManager.PERMISSION_GRANTED 常量为 0,

如果是未授权的权限,该方法返回结果是 PackageManager.PERMISSION_DENIED 常量为 -1。

第二步,对未授权权限 申请用户授权

//读取联系人的权限String permission = Manifest.permission.READ_CONTACTS;//检查权限是否已授权int hasPermission = checkSelfPermission(permission);//如果没有授权if (hasPermission != PackageManager.PERMISSION_GRANTED) {    //请求权限,此方法会弹出权限请求对话框,让用户授权,并回调 onRequestPermissionsResult 来告知授权结果    requestPermissions(new String[]{permission}, REQUEST_CODE_ASK_SINGLE_PERMISSION);}else {//已经授权过    //做一些你想做的事情,即原来不需要动态授权时做的操作    doSomething();}

需要调用 requestPermissions(@NonNull String[] permissions, int requestCode) 方法,该方法需要传入两个参数:

参数1:String[] permissions 是一个字符串数组,即要申请的权限的字符串数组,同时也说明了,可以一次性申请多个权限,用户会一个一个进行授权

参数2:int requestCode 是一个 int 常量,此处为代表请求码,在第三步中可能会用到

注意: requestPermissions 这个方法是异步执行的

第三步,处理授权结果

//回调方法@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    switch (requestCode) {//根据请求码判断是哪一次申请的权限        case REQUEST_CODE_ASK_SINGLE_PERMISSION:            if (grantResults.length > 0) {//grantResults 数组中存放的是授权结果                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {//同意授权                    //授权后做一些你想做的事情,即原来不需要动态授权时做的操作                    doSomething();                }else {//用户拒绝授权                    //可以简单提示用户                    Toast.makeText(RuntimePermissionDemo.this, "没有授权继续操作", Toast.LENGTH_SHORT).show();                }            }            break;        case REQUEST_CODE_ASK_MUTI_PERMISSIONS:            break;        default: super.onRequestPermissionsResult(requestCode, permissions, grantResults);    }}

焦点访谈 用动图说话

通过上图,我们至少可以看到以下几点:

  • 第一次点击检查权限,会弹出权限申请对话框,告知用户申请什么权限,用户可以选择拒绝(DENY)或者同意(ALLOW),第一次先拒绝,会弹吐司提示;

  • 第二次点击检查权限,也会弹出权限申请对话框,但是多了一个 “不再询问(Never ask again)”,这一次还是点拒绝,还是会弹吐司提示;

  • 第三次点击检查权限,对话框同上,但这次选择同意,同意后我的操作只是在界面上显示一句话。

  • 第四次点击检查权限,就不会再弹出对话框了,因为已经授权过了。

但是,如果在第二次点击的时候,选了 “不再询问”,并且 “拒绝” 授权,那么情况会怎样?

上图可以看到不再询问后,就不再弹窗授权,而直接未授权转到回调方法中,这样就不能保证用户体验了

所以,我们总得想办法,在系统不提示弹窗授权的情况下,为用户提供别的授权渠道 —— 跳转到应用设置界面,手动打开某些权限

但是,问题是我们怎么判断用户选了 “不再提醒” 呢?这时候可能就需要使用 shouldShowRequestPermissionRationale(String permission)
这个方法了,需要注意的是这个方法在不同的情况下,返回值是不同的:

  • 在第一次请求权限前 即 第一次调用 requestPermissions(…) 方法前,调用会返回 false

  • 第一次请求权限时,用户拒绝了后,再调用返回 true

  • 第二次请求权限时,用户拒绝了,并选择了 “不再提醒” 的选项,再调用返回 false,

    如果不选择 “不再提醒” ,再调用还是返回 true

  • 手动在应用设置界面取消权限, 再调用返回 true

  • 如果已经授权再调用返回 false

对于这个方法什么时候使用,返回值怎么使用,网上也是说法不一,也确实需要仔细的推敲各种复杂情况,但我认为终归的目的还是:在没有权限操作的时候,要为用户提供开启权限的方法

这里我只给出个人认为比较合适的使用方式:我认为在请求权限回调方法 即 onRequestPermissionsResult(…) 中使用比较合理,下面是修改后的回调方法

@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    switch (requestCode) {        case REQUEST_CODE_ASK_SINGLE_PERMISSION:            if (grantResults.length > 0) {//grantResults 数组中存放的是授权结果                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {//同意授权                    //授权后做一些你想做的事情,即原来不需要动态授权时做的操作                    doSomething();                }else {//用户拒绝授权                    //可以简单提示用户                    Toast.makeText(RuntimePermissionDemo.this, "没有授权继续操作", Toast.LENGTH_SHORT).show();                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {                        //调用 shouldShowRequestPermissionRationale() 方法                        boolean shouldShow = shouldShowRequestPermissionRationale(permissions[0]);                        if (!shouldShow) {// shouldShow = false                             //需要弹出自定义对话框,引导用户去应用的设置界面手动开启权限                            showMissingPermissionDialog();                        }                    }                }            }            break;        case REQUEST_CODE_ASK_MUTI_PERMISSIONS:            break;        default: super.onRequestPermissionsResult(requestCode, permissions, grantResults);    }}

自定义对话框,引导用户去应用设置界面进行授权

/** * 需要手动开启缺失的权限对话框 */private void showMissingPermissionDialog() {    AlertDialog.Builder builder = new AlertDialog.Builder(this);    builder.setTitle("提示");    builder.setMessage("当前应用权限不足。\n\n可点击\"设置\"-\"权限\"-打开所需权限。\n\n最后点击两次后退按钮,即可返回。");    builder.setNegativeButton("知道了", null);    builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {        @Override        public void onClick(DialogInterface dialog, int which) {            startAppSettings();        }    });    builder.setCancelable(false);    builder.show();}/** * 启动应用的设置 来手动开启权限 */private void startAppSettings() {    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);    intent.setData(Uri.parse("package:" + getPackageName()));    startActivity(intent);}

修改后的代码应该可以满足我能想到所有的情况:

  1. 如果第一次申请授权,系统申请授权对话框会出现,如果用户拒绝,那么可以什么都不做

  2. 接着第二次申请授权,系统授权对话框仍会出现,而且有 “不再提示” 选项,

    如果用户只拒绝授权,不选择 “不再提示” ,那么还是可以什么都不做

    如果用户既拒绝授权,又选择 “不再提示” ,那么就可弹出自定义提示框,引导用户去设置开启权限

  3. 用户选择 “不再提示” 后再申请授权,系统授权对话框不会再出现,这时自定义提示框会就派上用场了,可引导用户去开启权限

  4. 如果已授权,那么就可直接进行操作了

  5. 如果已授权,但是用户在应用设置界面手动取消权限,这时再申请授权,系统对话框会提示,剩下的就和情况 2 是一样的了

效果图:

p.s. 顺带补充一下调用 requestPermissions(…) 弹出系统授权对话框的情况

  • 即使已经授权,再调用也会弹出对话框

  • 手动在设置界面取消权限,再调用也会弹对话框

  • 只要没有选择 “不再提醒” 就会弹出对话框,如果选了,那么不弹窗,回调结果是拒绝权限

同时申请多个权限

同时申请多个权限和申请单个权限的步骤是一样的,可先检查去除某些已授权过的,不重复申请,然后系统会在弹窗中依次展示要申请的权限,用户都选择后,结果也可在回调方法中处理

申请授权代码如下:

//申请 读联系人、读短信、获取定位的权限String[] mutiPermissions = new String[]{Manifest.permission.READ_CONTACTS,Manifest.permission.READ_SMS,Manifest.permission.ACCESS_FINE_LOCATION};//需要请求授权的权限ArrayList<String> needRequest = new ArrayList<>();//遍历 过滤已授权的权限,防止重复申请for (String permission : mutiPermissions) {int check = checkSelfPermission(permission);if (check != PackageManager.PERMISSION_GRANTED) {   needRequest.add(permission);   Log.d("RuntimePermissionDemo","needCheck: " + permission);}}//如果没有全部授权if (needRequest.size() > 0) {//请求权限,此方法异步执行,会弹出权限请求对话框,让用户授权,并回调 onRequestPermissionsResult 来告知授权结果requestPermissions(needRequest.toArray(new String[needRequest.size()]), REQUEST_CODE_ASK_MUTI_PERMISSIONS);}else {//已经全部授权过//做一些你想做的事情,即原来不需要动态授权时做的操作doSomething();}

回调处理代码:

@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    switch (requestCode) {        case REQUEST_CODE_ASK_SINGLE_PERMISSION://请求单个权限            //...            break;        case REQUEST_CODE_ASK_MUTI_PERMISSIONS://请求多个权限            if (grantResults.length > 0) {                //被拒绝的权限列表                ArrayList deniedPermissions = new ArrayList<>();                for (int i=0; iif (grantResults[i] != PackageManager.PERMISSION_GRANTED) {                        deniedPermissions.add(permissions[i]);                        Log.d("RuntimePermissionDemo", "Denied Permission: " + permissions[i]);                    }                }                if (deniedPermissions.size() <= 0) {//已全部授权                    doSomething();                }else {//没有全部授权                    Toast.makeText(RuntimePermissionDemo.this, "缺少部分权限", Toast.LENGTH_SHORT).show();                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {            //需要引导用户手动开启的权限列表                        ArrayList needShow = new ArrayList<>();                        //从没有授权的权限中判断是否需要引导用户                        for (int i=0; iif (!shouldShowRequestPermissionRationale(permission)) {                                needShow.add(permission);                                Log.d("RuntimePermissionDemo", "needShow: " + permission);                            }                        }                        //需要引导用户                        if (needShow.size() > 0) {                            //需要弹出自定义对话框,引导用户去应用的设置界面手动开启权限                            showMissingPermissionDialog();                        }                    }                }            }            break;        default: super.onRequestPermissionsResult(requestCode, permissions, grantResults);    }}

需要说明的是,可能你一次申请的多个权限,如果只有部分满足,也不影响应用的使用,这个就需要结合具体应用需求由开发者自行判断了,我这里给出的是都授权才能继续执行的示例。

效果图:

第三方库

既然申请权限是这么繁琐的的工作,那么肯定是需要好好封装一下的,然而我这种水平… 啧~ 封装的也就我自己能用罢了,我始终相信,肯定有大牛已开源了封装好了的第三方库,不信你在 GitHub 上 搜一下 “android permission” 试试!

虽然 GitHub 上已有了第三方库方便开发,但我依然还认为,自己得知道最原始的方法怎么使用才是最重要的。

部分源码

RuntimePermissionDemo.java

总结

动态权限申请对于用户而言是友好的,但受伤的总是程序猿…

总的来说,动态申请权限在以后的开发中是逃避不了的,还是趁机好好攻略一下,毕竟研究了一下发现,也不是特别难开发的东西

更多相关文章

  1. Android(安卓)调用系统搜索框
  2. 基于android系统的电话拨号跟短信发送
  3. Android(安卓)5.0 6.0 7.0 8.0 9.0新特性
  4. 解决绕过android下apk使用usb设备权限查询相应问题,自动获取usb权
  5. Android登录界面的实现
  6. 安卓第一行代码总结(一)
  7. Android——屏幕旋转时数据丢失问题解决方案
  8. Android的四大组件之三--Activity(4)----->Activity的启动方式和相
  9. 第一章 JAVA入门(Android安全模型)

随机推荐

  1. 自定义Android(安卓)ORM 框架greenDAO数
  2. Android(安卓)Annotations 仿微博Tabbar
  3. 语音识别(加注释)
  4. MQTT客户端移植Android
  5. 简单封装HttpUrlConnection异步请求
  6. How to upload Android(安卓)Studio proj
  7. 自定义AnimationDrawable动画播放完监听
  8. 地图的使用
  9. Android短信插入器源码
  10. Android学习笔记之数据持久化