Android AOP
Aop,简单的说就是面向切面编程。什么叫面向切面呢?举个例子,比如说你要给所有click事件添加一个避免连续点击的功能,方法很简单,写完后放到一个工具类中,然后事件中调用,触发连续点击了就return掉。这样做不是很好,有几个问题需要考虑的。第一:所有点击事件中都要添加,这个比较麻烦,代码冗余。第二:由于需要手动添加,难免会有漏掉的地方,排查也不方便。第三:假如以后不需要这个功能了,删除的过程也会让你崩溃。所以这时候Aop登场了,Aop可以简单的实现,而不需要去侵入到业务层代码,只需要知道我要面向的功能-点击事件-这个切面就Ok了,针对这个切面的内容,再进行下一步的处理,然后在编译的过程中,会自动将处理的代码编译到每一个点击事件中,完全无侵入的实现功能。
集成
我们主要讲解AspectJ。采用沪江网开源的AspectJ插件,地址:https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
介绍
A Android gradle plugin that effects AspectJ on Android project and can hook methods in Kotlin, aar and jar file.
集成步骤主要有两步:
- 第一步,在工程目录下的gradle中添加:
dependencies {
.....
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
.......
}
- 第二步,在app的gradle中添加
apply plugin: 'android-aspectjx'
apply plugin: 'kotlin-kapt'
并引用
dependencies {
.....
implementation 'org.aspectj:aspectjrt:1.8.9'
....
}
编译后就可以使用AspectJ了
JoinPoint
Joint Point | 含义 |
---|---|
Method call | 方法被调用 |
Method execution | 方法执行 |
Constructor call | 构造函数被调用 |
Constructor execution | 构造函数执行 |
Static initialization | static 块初始化 |
Field get | 读取属性 |
Field set | 写入属性 |
Handler | 异常处理 |
Pointcut
Pointcuts是具体的切入点,基本上Pointcuts是和JoinPoint相对应的。
Joint Point | Pointcuts 表达式 |
---|---|
Method call | call(MethodPattern) |
Method execution | execution(MethodPattern) |
Constructor call | call(ConstructorPattern) |
Constructor execution | execution(ConstructorPattern) |
Static initialization | staticinitialization(TypePattern) |
Field get | get(FieldPattern) |
Field set | set(FieldPattern) |
Handler | handler(TypePattern) |
Pattern类型
Pattern类型 | 语法 |
---|---|
MethodPattern | [!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型] |
ConstructorPattern | [!] [@Annotation] [public,protected,private] [final] [类名.]new(参数类型列表) [throws 异常类型] |
FieldPattern | [!] [@Annotation] [public,protected,private] [static] [final] 属性类型 [类名.]属性名 |
TypePattern | 其他 Pattern 涉及到的类型规则也是一样,可以使用 '!'、''、'..'、'+','!' 表示取反,'' 匹配除 . 外的所有字符串,'*' 单独使用事表示匹配任意类型,'..' 匹配任意字符串,'..' 单独使用时表示匹配任意长度任意类型,'+' 匹配其自身及子类,还有一个 '...'表示不定个数 |
切入点指示符
切入点指示符是用来指示切入点表达式的目的,有大概以下几种:
- execution:用于匹配方法执行的连接点;
- within:用于匹配指定类型内的方法执行;例如:within(cn.javass..*) cn.javass包及子包下的任何方法执行
- this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
- target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
- args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
- @within:用于匹配持有指定注解类型内的方法
- @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
- @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
- @annotation:用于匹配当前执行方法持有指定注解的方法
我们主要讲解下execution的语法及其使用方式:
execution切入点
execution是用于匹配方法执行的连接点,换句话说,就是指定我们要hock的点,那么hock的完成写法就是execution(* android.view.View.OnClickListener(..))
execution的语法:
execution(注解?修饰符?返回值类型 类型声明? 方法名(参数) 异常?)
-
注解:可选,如要hock注解,则必须添加。例如 execution(@java.lang.Override * *(..))
-
修饰符:可选,如public,proteted,写在返回值前面;
-
返回值类型:必选,可以使用*来代表任意返回值
-
类型声明:可选,可以是任意类型;
-
方法名:必须,可以用*来代表任意方法;
-
参数:() 代表没有参数,(..)代表匹配任意数量,任意类型的参数,也可以指定类型的参数进行匹配。
-
异常:可选,语法:"throws 任意异常类型",可以是多个,用逗号分隔,例如:throws java.lang.IllegalArgumentException,java.lang.ArrayIndexOutOfBoundsException.
通过execution()定义切点的不同方式
- 通过方法签名定义切点
execution(public * * (..))
匹配所有目标类的public方法。第一个代表返回类型,第二个代表方法名,而..代表任意的参数
execution( * *To(..))
匹配目标类所有以To为后缀的方法。第一个代表返回类型,而To代表任意以To为后缀的方法,而..代表任意参数的方法
- 通过类定义切点
execution( * com.aop.aspectj.cleaner.*(..))
- 通过类包定义切点
execution( * com.yue.service.imple..*.*(..))
第一个*代表返回值的类型是任意的
com.yue.service.imple 代表Aop所切的服务包名
包名后面的.. 代表当前包及子包
第二个*表示类名,*即所有类
.*(..)表示任何方法名,括号表示参数,两个点表示任何参数类型
注意:
在类名模式中,
.*表示包下的所有类,
..*表示包,子孙包下的所有类.
使用
以hock点击事件为例:
在hock没一个事件之前,我们都要先搞清楚切点是什么?点击事件的切点就比较简单,写法如下:
@Pointcut("execution(* android.view.View.OnClickListener.onClick(..))")
fun methodAnnotated() {
}
- @Pointcut,切点。意思就是我们要在android.view.View.OnClickListener.onClick这个路径下的onClick方法中,插入methodAnnotated()方法。
插入的方法是在onClick()方法的什么位置执行呢?方法前?方法后?还是需要阻断式执行?第二步便是我们要定义我们方法的执行位置,以及执行的具体方法代码.
@After("methodAnnotated")
fun clickAspect(JoinPoint joinPoint){
}
-
After:在插装方法执行后执行要插入的代码
-
Before:在插装方法执行前执行要插入的代码
-
Around:环绕方法执行。用于替代原有代码,可以进行代码阻断调用,控制原方法的调用时间;
-
AfterReturing;在方法执行后,返回一个结果在执行,如果没结果,则不会执行;
-
AfterThrowing:在方法执行过程中抛出异常后执行,也就是方法执行过程中,如果抛出异常后,才会执行此切面方法.
After和Before使用方式都一样,唯一区别的就是Around,它的使用跟其他几种区别比较大。因为Around可以自己来控制原始代码的执行与否,所以可以进行阻断式插入代码,入参也与其他不同,需要使用ProceedingJoinPoint,如:
@Around("methodAnnotated()")
@Throws(Throwable::class)
fun aroundJoinPoint(joinPoint: ProceedingJoinPoint) {
// 取出方法的注解
val methodSignature = joinPoint.signature as MethodSignature
val method = methodSignature.method
if (!method.isAnnotationPresent(AopOnClick::class.java)) {
return
}
val aopOnclick = method.getAnnotation(AopOnClick::class.java)
// 判断是否快速点击
if (!AopClickUtil.isFastDoubleClick(aopOnclick.value)) {
// 不是快速点击,执行原方法
joinPoint.proceed()
}
}
proceedingJoinPoint是JoinPoint的一个子接口,定义如下:
public interface ProceedingJoinPoint extends JoinPoint {
/**
* Proceed with the next advice or target method invocation
*
* @return
* @throws Throwable
*/
public Object proceed() throws Throwable;
/**
* Proceed with the next advice or target method invocation
* <p/>
* <p>Unlike code style, proceed(..) in annotation style places different requirements on the
* parameters passed to it. The proceed(..) call takes, in this order:
* <ul>
* <li> If 'this()' was used in the pointcut for binding, it must be passed first in proceed(..).
* <li> If 'target()' was used in the pointcut for binding, it must be passed next in proceed(..) -
* it will be the first argument to proceed(..) if this() was not used for binding.
* <li> Finally come all the arguments expected at the join point, in the order they are supplied
* at the join point. Effectively the advice signature is ignored - it doesn't matter
* if a subset of arguments were bound or the ordering was changed in the advice signature,
* the proceed(..) calls takes all of them in the right order for the join point.
* </ul>
* <p>Since proceed(..) in this case takes an Object array, AspectJ cannot do as much
* compile time checking as it can for code style. If the rules above aren't obeyed
* then it will unfortunately manifest as a runtime error.
* </p>
*
* @param args
* @return
* @throws Throwable
*/
public Object proceed(Object[] args) throws Throwable;