一、什么是AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
二、AOP 使用场景
主要用在公共业务上,例如:
- 登录判断
- 网络判断
- 权限获取
- 数据校验
- 日志输出
- 性能监控
- 按钮防抖
三、AOP 基本知识点
- 通知、增强处理(Advice)
就是你想要的功能,也就是上说的登录判断、数据校验等。你给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码。 - 连接点(JoinPoint)
就是允许你通知(Advice)的地方,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点。 - 切入点(Pointcut)
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。 - 切面(Aspect)
切面是通知和切入点的结合。现在发现了吧,没连接点什么事,连接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过before,after,around等AOP注解就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
三、AOP 实现方式
- AspectJ
一个 JavaTM 语言的面向切面编程的无缝扩展(适用Android)。 - Javassist for Android
用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。 - DexMaker
Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。 - ASMDEX
一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。
四、AOP 注解
- @Aspect(切面)
声明切面,标记类 - @Pointcut(切点表达式)
定义切点,标记方法 - @Before(切点表达式)
前置通知,切点之前执行 - @Around(切点表达式)
环绕通知,切点前后执行 - @After(切点表达式)
后置通知,切点之后执行 - @AfterReturning(切点表达式)
返回通知,切点方法返回结果之后执行 - @AfterThrowing(切点表达式)
异常通知,切点抛出异常时执行
四、Android 实现AOP方式之AspectJ
- 项目添加插件路径
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
- app添加gradle依赖和导入插件
apply plugin: 'android-aspectjx'
api 'org.aspectj:aspectjrt:1.8.9'
五、双击拦截实现
- 定义点击注解类
@Target({ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickLimit {
int value() default 500;
}
- 定义切面类和功能实现
@Aspect
public class ClickLimitAspect {
private static final int CHECK_FOR_DEFAULT_TIME = 500;
private static final String POINTCUT_ON_ANNOTATION =
"execution(@com.xuetian.xtuikit.click.annotation.ClickLimit * *(..))";
@Pointcut(POINTCUT_ON_ANNOTATION)
public void onAnnotationClick(){}
@Around("onAnnotationClick()")
public void processJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
try {
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)){
joinPoint.proceed();
return;
}
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
boolean isHasLimitAnnotation = method.isAnnotationPresent(ClickLimit.class);
String methodName = method.getName();
int intervalTime = CHECK_FOR_DEFAULT_TIME;
if (isHasLimitAnnotation){
ClickLimit clickLimit = method.getAnnotation(ClickLimit.class);
int limitTime = clickLimit.value();
if (limitTime <= 0){
joinPoint.proceed();
return;
}
intervalTime = limitTime;
}
Object[] args = joinPoint.getArgs();
View view = getViewFromArgs(args);
if (view == null) {
joinPoint.proceed();
return;
}
Object viewTimeTag = view.getTag(R.integer.xt_click_limit_tag_view);
if (viewTimeTag == null){
proceedAnSetTimeTag(joinPoint, view);
return;
}
long lastClickTime = (long) viewTimeTag;
if (lastClickTime <= 0){
proceedAnSetTimeTag(joinPoint, view);
return;
}
if (!canClick(lastClickTime, intervalTime)){
return;
}
proceedAnSetTimeTag(joinPoint, view);
} catch (Throwable e) {
e.printStackTrace();
joinPoint.proceed();
}
}
public void proceedAnSetTimeTag(ProceedingJoinPoint joinPoint, View view) throws Throwable {
view.setTag(R.integer.xt_click_limit_tag_view, System.currentTimeMillis());
joinPoint.proceed();
}
public View getViewFromArgs(Object[] args) {
if (args != null && args.length > 0) {
Object arg = args[0];
if (arg instanceof View) {
return (View) arg;
}
}
return null;
}
public boolean canClick(long lastClickTime, int intervalTime) {
long currentTime = System.currentTimeMillis();
long realIntervalTime = currentTime - lastClickTime;
return realIntervalTime >= intervalTime;
}
}