Android 面向切面编程 AOP 解决连续点击打开重复页面问题

先介绍概念java

好比我但愿在全部页面启动的时候加一个埋点~ 但愿在全部按钮点击的时候加个快速重复点击的判断~等等 这样在项目中同一种类型的全部代码处,统一加入逻辑处理的方法,叫作 面向切面编程 AOPandroid

而这些咱们须要插入代码的具体位置,则叫作切点 Pointcut,好比我在某些类的某个方法中插入git

项目中能够插入地方的类型,叫作链接点 Join Point,好比我能够在方法中插入,能够在变量取值时插入github

插入的方式 Advice,可让咱们指定在切点前插入,仍是在切点执行后插入等sql

这些后面都会具体介绍编程

Android实现AOP,可使用的方案主要有两个app

一个是大神的 github.com/JakeWharton…maven

一个是沪江的 github.com/HujiangTech…学习

都是基于 aspectJ 的,因此也能够直接配置aspectJ,不过太麻烦~gradle

咱们以Hugo为例,采坑之旅如今开始~


先配置

项目 build.gradle

buildscript {

  repositories {

    mavenCentral()

  }

  dependencies {

    classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'

  }

}
复制代码

app / build.gradle

apply plugin: 'com.jakewharton.hugo'
复制代码

代码中能够设置开启

Hugo.setEnabled(true|false)
复制代码

注意

1.若是有引用module,须要在module中添加以上配置和编写代码

2.不支持lambda

实践~

好比要解决 快速点击打开多页面 的问题

配置好后,开始编写代码~

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Around("call(* android.content.Context.startActivity(..))")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

}
复制代码

这个类文件保存在依赖module(没有就在主app module中)中任意package下就好了。

不用任何配置或其余代码处理,不用修改原有代码~ 而后直接run项目,就能够了~

这样,代码中全部context.startActivity的地方就都会先判断是不是快速点击,而后再执行,达到防止重复打开页面的目标


解释下代码

@Aspect 标注AOP类,表示该类里面是处理切面代码的,固定写法

Advice 切点插入方式。表示在匹配的切点处,用什么方式去处理,一共有以下几个类型

  • @Around 环绕插入。参数为ProceedingJoinPoint,能够手动包裹代码后,在须要的条件中调用参数的方法 proceed() 表示执行目标方法
  • @Before 前置插入。在切点前执行
  • @After 后置插入。在切点后执行
  • @After returning。在返回值以后执行
  • @After throwing。在抛出异常后执行

注意: 只有Around参数是ProceedingJoinPoint,须要调用proceed执行方法,其余的都只是先后插入,不会影响原有代码的执行

因此埋点功能的话咱们就可使用after或before在原有方法先后执行埋点请求;

而防止连续跳转页面,就可使用Around,而后在判断条件里手动 proceed 调用原方法

Join Point 链接点。表示咱们能够插入代码的位置类型,和Pointcuts切点结合使用

  • Method call。方法被调用。结合切点写法:call(方法切点正则)
  • ** Method execution**。方法被执行。结合切点写法:execution(方法切点正则)
  • Constructor call。构造方法被调用。结合切点写法:call(构造方法切点正则)
  • Constructor execution。构造方法被执行。结合切点写法:execution(构造方法切点正则)
  • Field get。属性读取。结合切点写法:get(变量切点正则)
  • Field set。属性设置。结合切点写法:set(变量切点正则)
  • Pre-init。初始化前。结合切点写法:preinitialization(构造方法切点正则)
  • Init。初始化。结合切点写法:initialization(构造方法切点正则)
  • Static init。静态代码块初始化。结合切点写法:staticinitialization(对应代码切点正则)
  • Handler。异常处理。结合切点写法:handle(对应代码切点正则)
  • Advice execution。全部Advice执行。结合切点写法:adviceexecution()

最经常使用的是 method call 和 execution,通常系统类的方法直接用call,@Around(call(xxx))包裹处理;

若是是自定义方法,但愿里面插入,就@Before(execution(xxx))

Poincuts 切点。是一段匹配规则,表示须要切入代码的地方,规则以下 @注解 访问权限 返回值类型 包名.方法名(方法参数)

  • @注解 可选。能够用来匹配指定注解的切点,也能够自定义个注解在须要特殊处理的地方标注
  • 访问权限 可选。就是 public private static 等,不加的话就是全匹配

后面返回值、包名什么的,支持通配符 * .. + 等

  • * 表示匹配任意内容。 好比 包名中使用。java.*.Date 能够表示 java.sql.Date也能够表示java.utils.Date 单独使用。返回值若是是 * 表示任意类型返回 拼接使用。*Dialog 表示匹配任意 XXDialog内容
  • .. 表示匹配任意类型任意数量内容。好比 包名中使用。com..Utils 表示java任意包以及子包下的 Utils类 参数中使用。(..)表示匹配任意类型任意数量的参数,也能够(String, ..) 指定第一个,其余的不定
  • + 表示子类。好比 java..*Model+,表示在java任意包或子包下以Model结尾类的子类

因此翻译下咱们以前代码的核心方法部分

@Around("call(* android.content.Context.startActivity(..))")
复制代码

就是在系统context.startActivity方法调用(call)的时候,环绕插入代码(@Around),

方法内处理具体实现,判断是不是快速点击,若是非快速点击才正常执行ProceedingJoinPoint.proceed()

但代码还有些问题,就是 Context.startActivity并不能包含全部的状况,

还有Activity.startActivity,以及 startActivityForResult等没有覆盖到~ 这里就能够用咱们新学习的姿式解决,修改以下

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Around("call(* android..*.startActivity*(..))")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

}
复制代码

方法内不变,修改了匹配切点的规则

@Around("call(* android..*.startActivity*(..))")
复制代码

解释下就是,在 android.任意包或子包.. 下,的任意类*(能够是Activity、Context或Fragment),

调用该类的方法 startActivity*(包括startActivity方法和startActivityForResult方法)时,进行自定义处理

继续优化,若是但愿在打开FragmentDialog的时候,也要防止重复显示,那怎么办,

这时通配符不能包含两个区别较大的切点规则了,咱们能够申明多个切点,而后用逻辑符号拼接起来

切点申明很简单,直接用 @Pointcut 申明一个空方法,@Pointcut后也能够直接加上 链接点(切点规则)

多个方法对应多个切点,最后在须要处理的主方法内 @Around(切点规则方法1 || 切点规则方法2) 这样逻辑拼接起来

代码以下

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Pointcut("execution(* com.archex.core.base.BaseDialogFragment.show(..))")

    public void showBaseDialogFragment() {}

    @Pointcut("call(* android..*.startActivity*(..))")

    public void startActivity() {}

    @Around("showBaseDialogFragment() || startActivity()")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

}
复制代码

到此简单使用就结束啦~

相关文章
相关标签/搜索