Android 6.0 运行时权限处理解析
1.概述
在6.0及以上版本我们的危险权限都需要在运行的时候去申请,之前都是在清单文件中配置即可,现在就不行了需要加代码申请。
对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装。
而在6.0以后,我们可以直接安装,当app需要权限是会给予用户提示用户可以选择同意和拒绝。
2.运行时权限的检测
2.1 差别
新的权限机制更好的保护了用户的隐私,Google将权限分为两类,
一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如访问网络等;
另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、打电话等等。
Dangerous permissions,危险权限都是一组一组的,这是个什么概念呢?
又或是有什么用呢?如果app运行在Android 6.x的机器上,对于授权机制是这样的。
如果你申请某个危险的权限,假设你的app早已被用户授权了同一组的某个危险权限,那么系统会立即授权,而不需要用户去点击授权。
比如你的app对READ_CONTACTS已经授权了,当你的app申请WRITE_CONTACTS时,系统会直接授权通过。
此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(ps:这个dialog是不能进行定制的)。
2.2 申请授权。
ContextCompat.checkSelfPermission:检测权限,先判断有没有这个权限。
ActivityCompat.requestPermissions:申请权限
// 打电话权限申请的请求码
private static final int CALL_PHONE_REQUEST_CODE = 0x0011;
// 方法返回值为PackageManager.PERMISSION_DENIED 或者 PackageManager.PERMISSION_GRANTED。
// 当返回GRANTED表示有该权限; DENIED表示没有该权限。
if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
// 没有该权限 申请打电话权限
// 三个参数: 第一个参数是 Context; 第二个参数是用户需要申请的权限字符串数组(可以传多个); 第三个参数是请求码 主要用来处理用户选择的返回结果
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE}, CALL_PHONE_REQUEST_CODE);
}else {
// 有该权限,直接打电话
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + 137XXXXXXXX);
intent.setData(data);
startActivity(intent);
}
// 如果用户同意或是拒绝那么会回调onRequestPermissionsResult(),别看错了不是onActivityResult()
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode,permissions, grantResults);
if(requestCode == CALL_PHONE_REQUEST_CODE){
if (grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted 通过 打电话
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + 10086);
intent.setData(data);
startActivity(intent);
} else {
// Permission Denied 被拒绝
Toast.makeText(this,"权限被拒绝了", Toast.LENGTH_SHORT).show();
}
}
}
系统的申请权限,缺点是写代码太多。
3.利用反射加注解的方式封装我们的权限处理框架, 减少代码量。
如何优化代码?
- 写到BaseActivity中
- 利用反射 + 注解方式。
注解和注释区别
注释: 给程序员看的
注解: 是给程序看的
@Target 代表放在什么位置 FIELD 放在属性上 METHOD 放在方法上 TYPE 放在类上
@Retention 代表什么时候检测 RetentionPolicy.SOURCE 编译时 RetentionPolicy.RUNTIME 运行
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface PermissionFail {
int requestCode(); // 请求码,唯一标识,用于找到对应的方法。
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface PermissionSuccess {
int requestCode(); // 请求码,唯一标识,用于找到对应的方法。
}
/**
* PermissionHelper.requestPermission(this, CALL_PHONE_REQUEST_CODE, new String[]{Manifest.permission.CALL_PHONE});
* PermissionHelper.with(this).requestCode(CALL_PHONE_REQUEST_CODE)
* .requestPermissions(Manifest.permission.CALL_PHONE).request();
*/
public class PermissionHelper {
private Object mObject; // 是Activity或Fragment。
private int mRequestCode;
private String[] mRequestPermissions;
private PermissionHelper(Object object) {
this.mObject = object;
}
// 一、传递什么参数,1.Fragment/Activity, 2.请求码, 3.需要请求的权限 String[]
public static void requestPermission(Activity activity, int requestCode, String[] permissions) {
PermissionHelper.with(activity).requestCode(requestCode)
.requestPermissions(permissions)
.request();
}
public static void requestPermission(Fragment fragment, int requestCode, String[] permissions) {
PermissionHelper.with(fragment).requestCode(requestCode)
.requestPermissions(permissions)
.request();
}
// 二.链式调用,传递参数。activity,fragment
public static PermissionHelper with(Activity activity) {
return new PermissionHelper(activity);
}
public static PermissionHelper with(Fragment fragment) {
return new PermissionHelper(fragment);
}
public PermissionHelper requestCode(int requestCode) {
this.mRequestCode = requestCode;
return this;
}
public PermissionHelper requestPermissions(String... permissions) {
this.mRequestPermissions = permissions;
return this;
}
// 3.0 真正判断和发起权限的请求。
public void request() {
// 3.1 判断是否6.0+,如果不是就直接执行方法,通过反射执行方法
if (!PermissionUtils.isOverMarshmallow()) {
// 执行什么方法? 只能通过采用注解的方式给方法打一个标记,然后通过反射去执行方法。直接执行方法。
PermissionUtils.executeSuccessMethod(mObject, mRequestCode);
return;
}
// 3.2 是大于6.0,先判断权限是否授予?
List<String> deniedPermissions = PermissionUtils.getDeniedPermission(mObject, mRequestPermissions);
// 3.2.1 授予了权限,就直接执行,通过反射执行。
if (deniedPermissions.size() == 0) {
// 所有权限都曾经授予了。直接执行方法。
PermissionUtils.executeSuccessMethod(mObject, mRequestCode);
} else {
// 3.2.2 没有授予权限,就申请权限。
// list 转数组。
String[] permissions = deniedPermissions.toArray(new String[deniedPermissions.size()]);
ActivityCompat.requestPermissions(PermissionUtils.getActivity(mObject),
permissions, mRequestCode);
}
}
/**
* 处理申请权限的回调 callback
* @param obj Activity或Fragment
* @param requestCode 请求码
* @param permissions 请求的权限
*/
public static void requestPermissionsResult(Object obj, int requestCode, String[] permissions) {
// 再次获取没授权的权限
List<String> deniedPermissions = PermissionUtils.getDeniedPermission(obj, permissions);
if (deniedPermissions.size() == 0) {
// 所有权限都同意了。直接执行方法。
PermissionUtils.executeSuccessMethod(obj, requestCode);
} else {
// 申请的权限中,有用户不同意的
PermissionUtils.executeFailMethod(obj, requestCode);
}
}
}
/**
* Added by Tom on 2024/07/16.
* 类里面的方法,都是静态方法。
* 处理权限请求的工具类。
*/
public class PermissionUtils {
private PermissionUtils() {
throw new UnsupportedOperationException("不能被实例化");
}
// 判断是否是6.0 以上的版本。
public static boolean isOverMarshmallow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
// 执行成功方法。
public static void executeSuccessMethod(Object reflectObject, int requestCode) {
// 获取class中所有的方法,遍历是否有 标记,并且要请求码一致。
Method[] methods = reflectObject.getClass().getDeclaredMethods();
for (Method method : methods) {
// 该方法上有没有打这个标记
PermissionSuccess successMethod = method.getAnnotation(PermissionSuccess.class);
if (successMethod != null) {
int methodCode = successMethod.requestCode();
if (methodCode == requestCode) {
// 找到了目标方法,去反射执行该方法。
executeMethod(reflectObject, method);
}
}
}
}
// 反射执行方法。
private static void executeMethod(Object reflectObject, Method method) {
// 执行方法,第一个是该方法属于哪个类, 第二个是参数。
try {
method.setAccessible(true); // 临时允许调用私有方法
method.invoke(reflectObject, new Object[]{});
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 获取未授予的权限(返回没有授予过的权限)
*
* @param object Activity或Fragment
* @return 没有授予过的权限
*/
public static List<String> getDeniedPermission(Object object, String[] requestPermissions) {
List<String> deniedPermissions = new ArrayList<>();
for (String requestPermission : requestPermissions) {
if (ContextCompat.checkSelfPermission(getActivity(object), requestPermission) ==
PackageManager.PERMISSION_DENIED) {
deniedPermissions.add(requestPermission);
}
}
return deniedPermissions;
}
public static Activity getActivity(Object object) {
if (object instanceof Activity) {
return (Activity) object;
} else if (object instanceof Fragment) {
return ((Fragment) object).getActivity();
}
return null;
}
/**
* 用户授权失败,执行的方法
* @param reflectObject Activity或Fragment
* @param requestCode
*/
public static void executeFailMethod(Object reflectObject, int requestCode) {
// 获取class中所有的方法,遍历是否有 标记,并且要请求码一致。
Method[] methods = reflectObject.getClass().getDeclaredMethods();
for (Method method : methods) {
// 该方法上有没有打这个标记 PermissionFail
PermissionFail failMethod = method.getAnnotation(PermissionFail.class);
if (failMethod != null) {
int methodCode = failMethod.requestCode();
if (methodCode == requestCode) {
// 找到了目标方法,去反射执行该方法。
executeMethod(reflectObject, method);
}
}
}
}
}
// Activity,Fragment中调用使用。
private static final int CALL_PHONE_REQUEST_CODE = 0x0011;
public void phoneClick(View view) {
// PermissionHelper.requestPermission(this, CALL_PHONE_REQUEST_CODE,
// new String[]{Manifest.permission.CALL_PHONE});
PermissionHelper.with(this).requestCode(CALL_PHONE_REQUEST_CODE)
.requestPermissions(Manifest.permission.CALL_PHONE).request();
}
@PermissionSuccess(requestCode = CALL_PHONE_REQUEST_CODE)
private void callPhone() {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "10086");
intent.setData(data);
startActivity(intent);
}
@PermissionFail(requestCode = CALL_PHONE_REQUEST_CODE)
private void callPhoneFail() {
Toast.makeText(this, "您拒绝了拨打电话!", Toast.LENGTH_SHORT).show();
}
// 可以放到BaseActivity中。减少代码。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults);
/* if(requestCode == CALL_PHONE_REQUEST_CODE){
if (grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted 通过 打电话
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + 10086);
intent.setData(data);
startActivity(intent);
} else {
// Permission Denied 被拒绝
Toast.makeText(this,"权限被拒绝了", Toast.LENGTH_SHORT).show();
}
}*/
PermissionHelper.requestPermissionsResult(this, requestCode, permissions);
}