从Android6.0开始,Android系统提供动态申请权限的机制, APP在使用危险权限时,须要用户的受权才可进一步操做。java
Android系统中权限申请的方式有两种,以下图所示: git
Android6.0之前的系统(API < 23)采用的这种方式,只要用户在AndroidManifest.xml中注册了权限,安装APP后默认就获取了这些权限。这种受权方式安全性极低,若是用户安装后没有关闭相应的权限,用户的私密数据很容易被哪些垃圾APP窃取。为了解决这种问题,国内的各大手机厂商为Android5.0如下的系统,针对某些权限作了必定的限制,即使在Android5.0如下,也须要用户进行手动受权才可以使用,这在某种程度上提升了安全性,但也因没有统一的标准,从而出现了各类兼容问题。github
随着系统的升级,Google也意识到静态申请权限的弊端,因此在Android6.0中,对权限进行了从新梳理,将权限分为普通权限和危险权限:数组
在Android开发中咱们应该尽量使用隔离式申请权限,用户没有受权时屏蔽相应功能便可。如今不少内容性APP,用户进去什么都没看到,就须要各类权限,没有权限还不能使用,这可能会引发用户的反感,固然,也是对用户安全意识的一种冲击,时间久了,用户可能会以为使用APP就应该受权,从而给一些恶意APP做恶的余地。安全
// ContextCompat.java
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
// PermissionChecker.java
public static int checkSelfPermission(@NonNull Context context,
@NonNull String permission)
复制代码
这两个方法都是检查权限是否获取的方法,但ContextCompat.checkSelfPermission在某些系统上(如基于Android8.0的MIUI10检查短信权限时)有bug, 不能准确判断权限是否已获取,此时可结合PermissionChecker.checkSelfPermission进行判断, 因此判断权限是否已获取可采用如下实现:ide
public static boolean hasPermission(@NonNull Context context, @NonNull String permission) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED
|| PermissionChecker.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
return true;
}
复制代码
当未获取权限时,须要向系统请求,请求时使用requestPermissions方法:工具
// ActivityCompat.java
// 在Activity中申请权限
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode)
// Fragment.java
// 在Fragment中申请权限
public final void requestPermissions(@NonNull String[] permissions, int requestCode)
复制代码
在Fragment使用ActivityCompat.requestPermissions申请权限时,若是用户拒绝了(且勾线了再也不提示)请求,Fragment中的onRequestPermissionsResult不会被回调,也就不能引导用户开启权限。因此在Fragment中应该使用Fragment的成员方法requestPermissions来请求权限。学习
// ActivtyCompat.java
// 检查APP是否应该向用于展现申请权限的解释
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission);
复制代码
此方法的返回值解释以下:this
因此在使用此方法时,咱们要先判断APP是否已申请过权限,不然难以判断返回false的两种状况。spa
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
复制代码
这是Activity和Fragment中申请权限的结果回调方法,其中permissions表示申请的权限数组,grantResults表示每一个权限的请求结果。取值为:
// 得到受权
public static final int PERMISSION_GRANTED = 0;
// 未获受权
public static final int PERMISSION_DENIED = -1;
复制代码
一般申请权限后的处理逻辑都是在该方法中实现。
对于不能同时知足以上条件的状况,默认使用的静态申请权限的方式,但不一样的ROM为了安全性,可能对其机制进行了修改,因此可能因ROM不一样而有所差别。
了解了申请权限的核心API,接下来就介绍一下在Activity中申请权限的实现过程,下面以点击申请拍照权限为例:
private void startPhoto() {
if (hasPermission(this, new String[]{Manifest.permission.CAMERA})) {
// 执行拍照的逻辑
} else {
ActivityCompat.requestPermissions(context, rnew String[]{Manifest.permission.ACCESS_FINE_LOCATION}),
PERMISSION_REQUEST_CODE);
}
}
复制代码
而后在onRequestPermissionsResult中监听权限申请的结果:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (hasPermission(permissions)) {
// 执行拍照的逻辑
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(context, permissions[refusedPermissionIndex])) {
// 向用户展现申请权限的理由
} else {
// 引用用户去开启权限
}
}
}
复制代码
上面只是一个申请权限的基本流程,真正实现时还要考虑多权限问题,版本的兼容问题,ROM的兼容问题等。
固然,在开发中也不会这样在每一个须要申请权限的Activity/Fragment中写这一段代码,即使封装成工具类也须要在每一个Activity/Fragment引用,耦合性过高。一般可将权限申请在一个透明的Activity中实现,这样在申请权限时,直接跳转到该Activity便可。下面推荐一个动态申请权限的库供参考。
PermissionManager是一个基于AOP实现的动态申请权限的开源库,目的是让申请权限的过程更简单。固然,也可当作学习Aspectj的的参考项目。具备如下优势:
具体的介绍可参考其源码地址:PermissionManager
因为Android碎片化严重,国内的各大厂商对权限这块也作了不一样的处理,因此在动态申请时确定存在失败的状况,若是在使用PermissionManager的时候发现问题,欢迎随时提issue & pull request。