当我们做权限判断和申请时,虽然有很多优秀的框架去帮我们完成,但是是否有想过这样的一个问题,以使用 RxPermissions 为例,每个权限判断的地方都要这样调用:
RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions
.request(Manifest.permission.CAMERA)
.subscribe(granted -> {
if (granted) { // Always true pre-M
// I can control the camera now
} else {
// Oups permission denied
}
});
是不是觉得非常麻烦呢,能否像 PermissionsDispatcher 那样,用注解去优雅的实现权限申请的问题,下面通过 AspectJ 简单的实现一下该功能。
首先,我希望权限申请效果是这样的:
// 点击一个按钮进行权限申请
findViewById(R.id.button)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
requestPermissions();
}
});
//通过注解可以同时申请一个或多个权限,当全部权限申请成功后会自动执行 requestPermissions 方法。
@GetPermissions({
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.CAMERA"
})
public void requestPermissions() {
Log.i("MainActivity", "==权限申请成功==");
}
//通过注解标记申请失败时的操作,并且在参数中获取到申请失败的权限
@OnPermissionDenied
public void requestPermissionsFail(List<String> failPermissions) {
Log.i("MainActivity", "==权限申请失败,失败的权限有:" + failPermissions.toString());
}
第一步
第一步先创建两个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetPermissions {
String[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnPermissionDenied {
}
第二步
创建 PermissionsAspect 类,并且加上 @Aspect 注解:
@Aspect
public class PermissionsAspect {
}
通过 @Pointcut 注解添加匹配规则,这里只是简单的匹配,在实际项目中,应该匹配更多的情况:
@Pointcut("execution(@com.lzx.aop.annotation.GetPermissions * *(..)) && @annotation(permission)")
public void methodGetPermissions(GetPermissions permission) {
}
使用 @Around 注解处理权限申请:
@Around("methodGetPermissions(permission)")
public void aroundPermissions(final ProceedingJoinPoint joinPoint, GetPermissions permission) {
}
我们发现,@Around 注解的参数是跟上面定义的方法一样的。下面看看在 aroundPermissions 方法里面需要做什么。
首先,因为权限申请回调需要在 Activity 或 Fragment 里面,所以这里我们先模仿 RxPermission 那样创建一个 Fragment 去做权限申请(代码最后贴出),这个Fragment 我们叫它 PermissionsFragment。创建 Fragment 需要先拿到 FragmentManager,所以我们先要得到 Activity 或者 Fragment 对象:
final Object object = joinPoint.getThis();
if (object instanceof AppCompatActivity) {
AppCompatActivity activity = (AppCompatActivity) object;
mPermissionsFragment = getLazySingleton(activity.getSupportFragmentManager());
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
mPermissionsFragment = getLazySingleton(fragment.getChildFragmentManager());
}
if (mPermissionsFragment == null) {
return;
}
final Context context = mPermissionsFragment.get().getActivity();
if (context == null) {
return;
}
然后获取到需要申请的权限数组,还有创建一个 list,用来存放申请失败的权限:
String[] permissions = permission.value();
final List<String> failPermissions = new ArrayList<>();
接下来开始权限申请:
mPermissionsFragment.get()
.requestPermissions(permissions)
.setPermissionsListener(new PermissionsFragment.OnRequestPermissionsListener() {
@Override
public void onRequestResult(String[] permissions, int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
int failNumber = 0;
boolean showGoToSettingActivity = false;
for (int i = 0, size = permissions.length; i < size; i++) {
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
boolean rationale = shouldShowRequestPermissionRationale[i];
if (!granted) {
failNumber++;
failPermissions.add(permissions[i]);
}
if (!granted && !rationale) {
showGoToSettingActivity = true;
}
}
goToSettingActivity(showGoToSettingActivity, context);
if (failNumber == 0) {
onPermissionGranted(joinPoint);
} else {
onPermissionDenied(object, failPermissions);
}
}
});
在申请权限的回调中,有三个参数,第一个是权限数组,它跟你传进去的权限数组是一样的,第二个参数,长度对应这第一个数组,它代表着是否申请成功,第三个数组,如果返回 true,则代表你点击了不再提醒的选项。
这里的逻辑是这样的:
- 拿到每个权限的申请状态(成功或失败)
- 拿到每个权限是否点击了不再提醒选项
- 如果权限申请不成功,则通过 failNumber 记录下来,并且存放在 failPermissions 列表中
- 如果申请失败并且点击了不再提醒选项则转跳到设置页面引导用户手动打开权限
- 如果所有权限都申请成功,则调用 onPermissionGranted 方法处理申请成功逻辑
- 如果有权限申请失败,则调用 onPermissionDenied 方法处理申请失败逻辑
goToSettingActivity:
//点击不再提醒后,引导用户至设置页手动授权
private void goToSettingActivity(boolean showGoToSettingActivity, Context context) {
if (showGoToSettingActivity) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", context.getApplicationContext().getPackageName(), null);
intent.setData(uri);
context.startActivity(intent);
}
}
onPermissionGranted:
//处理权限申请成功,执行申请后的方法
private void onPermissionGranted(ProceedingJoinPoint joinPoint) {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
这里直接调用 joinPoint.proceed(); ,会执行我们一开始所示的权限申请回调。
onPermissionDenied:
//处理权限申请失败
private void onPermissionDenied(Object object, List<String> failPermissions) {
Class<?> currentClass = object.getClass();
Method[] methods = currentClass.getDeclaredMethods();
if (methods.length == 0) {
return;
}
boolean isDeniedAnnotation;
for (Method method : methods) {
isDeniedAnnotation = method.isAnnotationPresent(OnPermissionDenied.class);
if (isDeniedAnnotation) {
method.setAccessible(true);//允许通过反射访问字段
OnPermissionDenied permissionDenied = method.getAnnotation(OnPermissionDenied.class);
if (permissionDenied == null) {
return;
}
handlerDeniedImpl(object, failPermissions, method);
break;
}
}
}
//通过反射调用注解方法
private void handlerDeniedImpl(Object object, List<String> failPermissions, Method method) {
Class<?>[] types = method.getParameterTypes();
try {
//方法没有参数时
if (types.length == 0) {
method.invoke(object);
} else {
method.invoke(object, failPermissions);
}
} catch (Exception e) {
e.printStackTrace();
}
}
申请失败处理主要逻辑是:
- 找到被注解 OnPermissionDenied 标记的方法
- 通过反射去回调方法
下面给出 PermissionsAspect 类的全部代码: