随着Android系统的不断升级,Android系统在系统安全这一块也有了一定的提升,比如在Android5.0之前,用户安装任何应用都必须无条件接受该应用在
AndroidManifest.xml
文件中声明的权限,否则就只能不安装此应用,这也给了一些别有用心的应用可乘之机,用户的隐私也没法得到保证。到了Android5.0开始,用户在安装应用时会出现一个复选框,让用户选择授予该应用的一些权限,不选择则该应用不会拥有该权限。进入Android6.0以后,Google进一步对权限进行了控制,对于一些危险权限,必须在使用时动态申请,由用户授权之后,应用才会拥有该权限,这更加加强了应用的安全性。但这个变化也使得我们以前可以正常运行的应用受到了一定的影响,我们必须对其做一定的适配才能保证我们的应用正常运行,下面我们就来一起学习一下Android6.0的权限管理机制。
一、正常权限和危险权限
下面是Android官网对
正常权限
和危险权限
的解释:
- 正常权限涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。如果应用声明其需要正常权限,系统会自动向应用授予该权限。
- 危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。
除了上面说的正常权限和危险权限,还有一个权限组
的概念需要了解,权限组
简单点理解就是讲权限进行分组,比如READ_CALENDAR
权限和WRITE_CALENDAR
权限属于CALENDAR
这个组。任何权限都可属于一个权限组,包括正常权限和应用定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组,所以可以说所有危险的 Android 系统权限都属于权限组。
下面列出危险权限及其所属的组:
权限组 | 权限 |
---|---|
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 |
除了上面的表,我们还可以在命令行中查看危险权限及其所属的组,在命令行中输入以下内容即可得到结果:
adb shell pm list permissions -g -d
二、应用请求危险权限的处理
下面我们说说应用在请求危险权限时系统的处理,如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:
- 如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。
- 如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限。
上面是Android官网的介绍,从中可以看到很重要的一点 系统只告诉用户应用需要的权限组,而不告知具体权限
三、代码实战权限管理机制
上面说了那么多理论,现在就开始真正的实战,其实就简单的下面几步即可完成,我们下面以申请打电话权限为例:
1 . 在AndroidManifest.xml
文件中添加需要的权限(适配Android6.0以下的系统)
在
AndroidManifest.xml
文件中添加如下权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
2 . 检查危险权限
我们在开始打电话之前必须先要检查我们是否拥有打电话的权限,对于正常的权限,系统默认是授予的,但对于打电话这种危险权限我们就必须先进行检查,此处调用
ContextCompat.checkSelfPermission()方法进行检查
,如果检查的结果是用户已经授予了我们此权限,那我们就可以直接调用打电话的逻辑,如果没有,那我们必须进行权限申请。
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED){
//做权限申请处理
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, CALL_PHONE_REQUEST_CODE);
}else{
//用户已经授权,直接处理业务逻辑打电话
doCallPhone();
}
3 . 申请授权
可以看出我们上面使用
ActivityCompat.requestPermissions()
方法申请权限,此方法的第二个参数可以传入一个权限数组,我们可以一次性申请多个权限,第三个参数是一个请求码,用于我们在下一步中根据申请的结果做回调处理。
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, CALL_PHONE_REQUEST_CODE);
4 . 处理申请回调
我们通过重写
onRequestPermissionsResult()
方法来处理回调结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case CALL_PHONE_REQUEST_CODE:
//打电话权限回调处理
if (grantResults[0] == PackageManager.PERMISSION_GRANTED){
//打电话权限被授予
doCallPhone();
}else {
//本次拒绝了权限请求,提示用户权限未被授予
Toast.makeText(this, "权限申请失败", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
上面的
requestCode
就是我们在申请权限时提供的请求码,grantResults
中存放着我们每个权限所对应的申请结果,由于我们只申请了一个权限,所以grantResults[0]
即可。
当然,如果你向用户申请一个权限,而用户又拒绝了你的权限,等下次用户申请的时候还是会弹出用户授权窗口,这个时候你或许想更加清楚地给用户解释一下你为什么使用该权限,这个时候就需要用到ActivityCompat.shouldShowRequestPermissionRationale()
这个方法。
这个方法会在应用首次安装时返回
false
,当用户拒绝了一次你的权限后也会返回true
注:当用户点击了授权窗口上的不再询问
复选框时,该方法会返回false
,你可以根据用户的行为来做出一些反应,但一般的权限申请窗口都对所申请的权限有一定的说明,除非特别情况,我们没有必要自己进行权限的说明解释。
四、更加优雅的实现权限申请及管理
说了上面那么多,你会发现权限申请最主要的就那么几步,但我们不能总是在每次需要申请的时候重复写代码,这个时候我们就可以去看看别人封装的库,其实也可以自己进行封装,下面推荐两个
PermissionGen
和MPermissions
-
PermissionGen
是一个韩国人封装的,项目链接在此 : https://github.com/lovedise/PermissionGen -
MPermissions
是鸿神封装的库,项目链接在此 : https://github.com/hongyangAndroid/MPermissions
上面的两个库都有详细的使用方法,这里不再赘述,第一个库是基于运行时注解,使用时会影响性能,第二个库是基于编译时注解,对性能不会有影响。
网友评论