Android AOP

2020-05-14  本文已影响0人  竖起大拇指

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.

集成步骤主要有两步:

dependencies {
          .....
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
          .......
        }
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的语法及其使用方式:

execution切入点

execution是用于匹配方法执行的连接点,换句话说,就是指定我们要hock的点,那么hock的完成写法就是execution(* android.view.View.OnClickListener(..))
execution的语法:

execution(注解?修饰符?返回值类型 类型声明? 方法名(参数) 异常?)
通过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() {

    }
@After("methodAnnotated")
fun clickAspect(JoinPoint joinPoint){
}

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;
上一篇 下一篇

猜你喜欢

热点阅读