美文网首页
Android AOP

Android AOP

作者: 竖起大拇指 | 来源:发表于2020-05-14 16:26 被阅读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.

集成步骤主要有两步:

  • 第一步,在工程目录下的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;

相关文章

网友评论

      本文标题:Android AOP

      本文链接:https://www.haomeiwen.com/subject/bzltohtx.html