先介绍概念
比如我希望在所有页面启动的时候加一个埋点~ 希望在所有按钮点击的时候加个快速重复点击的判断~等等 这样在项目中同一种类型的所有代码处,统一加入逻辑处理的方法,叫做 面向切面编程 AOP
而这些我们需要插入代码的具体位置,则叫做 切点 Pointcut ,比如我在某些类的某个方法中插入
项目中可以插入地方的类型,叫做 连接点 Join Point ,比如我可以在方法中插入,可以在变量取值时插入
而 插入的方式 Advice ,可以让我们指定在切点前插入,还是在切点执行后插入等
这些后面都会具体介绍
Android实现AOP,可以使用的方案主要有两个
一个是大神的 github.com/JakeWharton…
一个是沪江的 github.com/HujiangTech…
都是基于 aspectJ 的,所以也可以直接配置aspectJ,不过太麻烦~
我们以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执行方法,其他的都只是前后插入,不会影响原有代码的执行
所以埋点功能的话我们就可以使用after或before在原有方法前后执行埋点请求;
而防止连续跳转页面,就可以使用Around,然后在判断条件里手动 proceed 调用原方法
Join Point 连接点。表示我们可以插入代码的位置类型,和Pointcuts切点结合使用
最常用的是 method call 和 execution,一般系统类的方法直接用call,@Around(call(xxx))包裹处理;
如果是自定义方法,希望里面插入,就@Before(execution(xxx))
Poincuts 切点。是一段匹配规则,表示需要切入代码的地方,规则如下 @注解 访问权限 返回值类型 包名.方法名(方法参数)
后面返回值、包名什么的,支持通配符 * .. + 等
所以翻译下我们之前代码的核心方法部分
@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(); } } } 复制代码
到此简单使用就结束啦~