1.简介
在Android6.0之前版本,权限请求较为简单,仅在用户安装app时将自己需要使用的所有权限列出来告知用户,若用户授权,则app安装后可随时使用该权限。自6.0开始,一些涉及用户隐私的敏感权限需在使用时动态申请,且用户可选择授权或拒绝。当然,权限的改进对用户而言是好事,毕竟更能保护用户隐私。但对于开发者而言,也多了一项动态权限申请的工作
2.权限分类
对开发者而言,权限则主要分为以下两类:
1)普通权限(normal permissions): 只需在manifest中注册
2)危险权限(dangerous permissions):仍需在manifest中注册,但具体授权分以下几种情况
targetSdk<23 | targetSdk>=23 | |
---|---|---|
手机系统<23 | 安装时默认获得权限且用户无法在安装后取消权限 | 安装时默认获得权限且用户无法在安装后取消权限 |
手机系统>=23 | 安装时默认获得权限,但用户可在安装后取消授权( 取消时手机会提示用户该APP是为旧版手机打造,让用户谨慎操作 ) | 安装时不会获得权限而需在运行时向用户动态申请。用户授权后仍可在设置界面中取消,取消授权后在app运行过程中可能会出现crash |
由上表可知当APP的targetSdk>=23且运行在Android>=6.0(API23)的手机上时必须使用动态申请权限
具体危险权限如下:
3.运行时权限申请(使用系统提供的API)
1)权限检查
对于权限检查,Android提供了以下3种方式
1.ContextCompat#checkSelfPermission
2.Context#checkSelfPermission
3.PermissionChecker#checkSelfPermission
需注意的是若应用targetSdk<23,则第1、2种方式返回的永远是PERMISSION_GRANTED,即永远返回已授权。根据本文上面内容知当targetSdk<23时,应用虽在安装时就获得授权,但若运行在>=Android6.0的手机上时,用户可在安装后取消授权,此时就不能使用第1、2种方式来检查权限,而需使用第3种
public static boolean checkSelfPermission(String permission, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//getTargetVersion是判断app的targetSdk的方法
if (getTargetVersion(context) >= Build.VERSION_CODES.M) {
//应用的targetSdk>=23则使用Context#checkSelfPermission(permission)
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
} else {
//若targetSdk<23则使用PermissionChecker#checkSelfPermission
return PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
}
} else {//手机版本低于6.0的,安装后即授权且用户无法取消
return true;
}
}
2)请求权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
requestCode);
注意:1.Fragment中请求需使用自己的requestPermissions方法
3)处理请求结果
请求权限后系统会回调申请权限的Activity的onRequestPermissionsResult(),若使用的是Fragment的requestPermissions方法,则回调对应Fragment的onRequestPermissionsResult()
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode != this.requestCode || grantResults.length = 0) {
return;
}
for (int i = 0; i < grantResults.length; i++) {
String permission = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, permission + "已被授权", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, permission + "已被拒绝", Toast.LENGTH_SHORT).show();
if (shouldShowRequestPermissionRationale(permission)) {
//请求被用户拒绝但用户未勾选不再询问框,可继续请求权限
} else {
//请求被用户拒绝且用户勾选了不再询问框,需要用户前往设置中授权
}
}
}
}
4)总结
用系统提供的api进行申请步骤较为繁琐,且请求和处理代码不在相同位置,代码量多了的话可读性变差。那有没有什么好的方法既能简化流程提高可读性又能避免以上第二种情况呢?答案是肯定的,著名基佬交流网站github上就有丰富的权限请求库供各位客官享用!
4.使用EasyPermissions进行权限申请
1)特点
- 链式操作
- 请求前会自动检查是否已被授予 (这样在请求前就不必再进行权限检查了)
- 若请求的权限未在manifest中注册,将抛出明确的异常 (请求未在manifest注册的权限将导致不弹出dialog而直接返回false,有时我们可能对此十分懵逼,因为这既不报错也不弹出dialog代码也OK就是请求失败,可能要很久才反应过来忘了在manifest中注册)
- 自动重试(可配置项),配置该选项后若请求被拒但用户未勾选不再提示框时会自动重试直到用户授权或勾选不再提示框
2)依赖
A.在项目根build.gradle中
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
B.添加依赖
dependencies {
implementation 'com.github.Ficat:EasyPermissions:v2.1.0'
}
3)使用
//requestEach方式
EasyPermissions
.with(activity)
.requestEach(Manifest.permission.CAMERA)
.result(new RequestEachExecutor.ResultReceiver() {
@Override
public void onPermissionsRequestResult(Permission permission) {
String name = permission.name;
if (permission.granted) {
//name权限被授予
} else {
if (permission.shouldShowRequestPermissionRationale) {
//name权限被拒绝但用户未勾选不再提示框,可继续请求
} else {
//name权限被拒绝且用户勾选了不再提示框
//此时不能再次请求了,而需要user前往设置界面手动授权
EasyPermissions.goToSettingsActivity(activity);
}
}
}
});
//request方式,请求的所有权限被用户授权后返回true,否则返回false
EasyPermissions
.with(activity)
.request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.autoRetryWhenUserRefuse(true, new BaseRequestExecutor.RequestAgainListener() {//是否自动重试
@Override
public void requestAgain(String[] needAndCanRequestAgainPermissions) {
//该监听回调中传入的是再次请求的权限,用以在重新请求时弹出说明框等信息(如
//向用户说明为何要使用该权限)
for (String s : needAndCanRequestAgainPermissions) {
Log.e("TAG", "request again permission = "+s);
}
}
})
.result(new RequestExecutor.ResultReceiver() {
@Override
public void onPermissionsRequestResult(boolean grantAll, List<Permission> results) {
if (grantAll) {
Toast.makeText(MainActivity.this, "request permissions success!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "request permissions fail!", Toast.LENGTH_SHORT).show();
}
}
});