AspectJ打造权限申请框架
前言
一、AOP是什么?
- OOP是面向对象。
- AOP为Aspect Orented Programming的缩写,翻译为:面向切面编程,通过预编译方式和运行期间动态代码来实现程序功能的统一维护的技术。
假如我们现在有这样一个需求,我们需要对50个方法分别进行代码执行时间统计,在面向对象的思想里我们肯定是会写入如下的代码
fun1(){
long startTime = System.currentTimeMills();
...
long endTime = System.currentTimeMills();
Log.d("function cost time",endTime - startTime);
}
fun2(){
long startTime = System.currentTimeMills();
...
long endTime = System.currentTimeMills();
Log.d("function cost time",endTime - startTime);
}
fun3(){
long startTime = System.currentTimeMills();
...
long endTime = System.currentTimeMills();
Log.d("function cost time",endTime - startTime);
}
...
有没有发现我们在这几个方法里加了三行相同的代码,是不是感觉到优点重复,还有比较累人 ,而且我们虽然实现了我们想做的事情,但是并不优雅,添加/修改原有的代码其实并不好,所以面向对象并不适合,我们就要通过面向切面的方式。
@ExecetuTime
fun1(){
...
}
@ExecetuTime
fun2(){
...
}
@ExecetuTime
fun3(){
...
}
...
看上面的代码是不是优雅很多了,是利用AspectJx实现的(面向切面的框架,封装了AspectJ)重复的代码也不见了。我们并不需要自己实现面向切面,避免重复造轮子,有能力的小伙伴也可以自行研究,欢迎讨论。
AOP应用场景:
- 权限申请
- 日志统计
- 行为统计
- 性能检测
再来看一张图,这张图可以看出两种不同方式的编译流程,一个是用javac来编译,一个是AJC来编译,所以AsepctJ的AJC编译器帮我们做了许多事,实现了切面。
二、AspectJX实现权限申请框架
1.基本概念
AspectJ在程序的编译过程中通过它特有的AJC编译器将字节码文件中插入我们自己定义的切面代码。
- Pointcut(切入点)
告诉AspectJ你要在原有的业务逻辑的某一块插入自己的代码,插入的那个地方就叫切入点。标识。 - Advice(通知)
这个是你切入点插入的代码的具体逻辑实现的地方,典型的类型有before,after和around。
Advice类型 | 解释 |
---|---|
before | 在目标方法前执行这个代码 |
after | 在目标方法后执行这个代码 |
around | 替换目标方法执行的代码 |
- Joint point(连接点)
程序中可能作为代码注入目标的点。拿到了方法。
这里我用的是AspectJX,因为原生的AspectJ我不太喜欢,比较烦人。
2.引入AspcetJX
项目的Build.gradle
//aspectjtools插件
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
moudle的Build.gradle
apply plugin: 'com.hujiang.android-aspectjx'
implementation 'org.aspectj:aspectjrt:1.8.+'
如果是是作为lib方式引入的化,记得在app模块下也加入
apply plugin: 'com.hujiang.android-aspectjx'
implementation 'org.aspectj:aspectjrt:1.8.+'
3.项目结构
这里我把这个封装了一下,把它作为lib的方式,这样以后的项目直接移过去就好了。
类名 | 作用 |
---|---|
PermissionActivity | 权限申请的activity(透明的,用户看不见) |
RequestCallBackListener | 权限申请回调接口 |
PermissionUtils | 权限申请工具类 |
Permission | aop切入点 |
PermissionAspect | aop通知 |
PermissionDenied | 权限被拒绝并不再提示的方法 |
PermissionFailed | 权限被拒绝的方法 |
这里就介绍一个核心类PermissionAspect
//这个标注 意味着这个类由AJC编译器来编译
@Aspect
public class PermissionAspect {
// Permission 修饰的 任何返回值类型 任何方法名字 任何参数
//这里有一些通配符语法,不知道的小伙伴可以问一下度娘
@Pointcut("execution(@com.suyong.permissionslibrary.annotations.Permission * *(..)) && @annotation(permission)")
public void requestPermission(Permission permission) {
}
@Around("requestPermission(permission)")
public void getPermission(final ProceedingJoinPoint proceedingJoinPoint, Permission permission) {
Log.d("-------", "执行了权限请求");
Context context = null;
final Object aThis = proceedingJoinPoint.getThis();
if (aThis instanceof Context) {
context = (Context) aThis;
} else if (aThis instanceof Fragment) {
context = ((Fragment) aThis).getActivity();
}
if (context == null || permission.requestPermissionList() == null || permission.requestPermissionList().length == 0) {
return;
}
String[] permissions = permission.requestPermissionList();
int requestCode = permission.requestCode();
//启动隐藏的activity进行权限申请
PermissionActivity.start(context, permissions, requestCode, new RequestCallBackListener() {
@Override
public void permissionSuccess() {
//执行原来的代码逻辑
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void permissionCanceled() {
//利用防反射的方式运行PermissionFailed所标注的方法
PermissionUtils.invokeAnnotation(aThis, PermissionFailed.class);
}
@Override
public void permissionDenied() {
//利用防反射的方式运行PermissionDenied所标注的方法
PermissionUtils.invokeAnnotation(aThis, PermissionDenied.class);
}
});
}
}
我们在项目里优雅的使用例子:
@Permission(requestPermissionList = Manifest.permission.READ_EXTERNAL_STORAGE, requestCode = REQUEST_CODE)
public void queryPhoto(View view) {
Toast.makeText(this, "权限申请成功", Toast.LENGTH_SHORT).show();
}
@PermissionDenied(requestCode = REQUEST_CODE)
private void requestPermissionDenied() {
Toast.makeText(this, "权限申请失败,不在询问", Toast.LENGTH_SHORT).show();
}
@PermissionFailed(requestCode = REQUEST_CODE)
private void requestPermissionFailed() {
Toast.makeText(this, "用户拒绝了权限", Toast.LENGTH_SHORT).show();
}
具体的代码大家可以去下载 项目链接