最近项目进入紧锣密鼓测试阶段,昨天测试提了一个issue,app中按钮都没有做快速点击校验。

这就涉及到aop面向切面编程了!后端开发Spring对aop应该很熟悉,android开发中可能用到aop的情况没有后端那么多,但是aop对android开发也是至关重要的!

哪些情况用到aop?

比如针对某一功能进行埋点

全局日志处理

全局异常处理

全局动画处理等

java aop大致有三种方式

1.jdk动态代理

2.cglib动态代理

3.aspectj

.

jdk动态代理

cglib

aspectj

作用对象的限制

只能操作实现了接口的类

不能操作被final修饰的类,因为cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法来实现代理.

貌似没什么限制

基本原理

利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

采用基于jvm的ajc(编译器)和weaver(织入器),在class字节码中织入aspectj的代码

首先在project下的gradle文件中加入aspectjx插件

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {

repositories {

maven { url 'https://maven.aliyun.com/repository/jcenter/' }

maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }

google()

jcenter()

maven { url 'https://jitpack.io' }

maven { url 'https://dl.bintray.com/umsdk/release' }

}

dependencies {

classpath 'com.android.tools.build:gradle:3.3.1'

classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-beta02"

classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'

// NOTE: Do not place your application dependencies here; they belong

// in the individual module build.gradle files

}

}

allprojects {

repositories {

maven { url 'https://maven.aliyun.com/repository/jcenter/' }

maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }

google()

jcenter()

maven { url 'https://jitpack.io' }

maven { url 'https://dl.bintray.com/umsdk/release' }

}

}

task clean(type: Delete) {

delete rootProject.buildDir

}

ext {

compileSdkVersion = 28

minSdkVersion = 17

targetSdkVersion = 27

versionCode=5

versionName="1.4.6"

testRunner="1.1.1"

espresso="3.1.1"

junit="4.12"

appcompat="1.1.0-alpha01"

supportLibVersion = "28.0.0"

}

module下的gradle文件添加

apply plugin: 'android-aspectjx'

接下来就可以开始编写被@AspectJ 修饰的切面类了

AspectHandler.java 用来对点击事件相关的拦截处理

package com.mjt.pad.common.aspect;

import android.util.Log;

import com.mjt.common.utils.UIUtils;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

/**

* Copyright:mjt_pad_android

* Author: liyang

* Date:2019-05-05 15:41

* Desc:

*/

@Aspect

public class AspectHandler {

private static final String TAG = AspectHandler.class.getSimpleName();

@Before("execution(void android.view.View.OnClickListener.onClick(..))")

public void beforePoint(JoinPoint joinPoint) {

Log.e(TAG, "before: " + joinPoint);

}

@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")

public void dealWithNormal() {

}

@Around("dealWithNormal()")

public void onViewClicked(ProceedingJoinPoint proceedingJoinPoint) {

boolean isFastClickPassed = !UIUtils.isFastClickOnlyInAspect();

Log.e(TAG, "onViewClicked: 捕获到了,isFastClick=" + !isFastClickPassed);

if (isFastClickPassed) {

Log.e(TAG, "onViewClicked: " + proceedingJoinPoint);

try {

proceedingJoinPoint.proceed();

} catch (Throwable throwable) {

throwable.printStackTrace();

Log.e(TAG, "onViewClicked: ", throwable);

}

}

}

}

简单介绍下这个类里面的@Before @Pointcut @Around几个注解吧,不然完全没接触过aspectj的同学会看的一头雾水

首先说pointcut

@Pointcut相当于你要拦截的某些执行点或者调用点,pointcut可以有call,execution,target,this,within,withincode等等操作符,这些操作符可以结合java的||,&&,!使用

call捕获的joinpoint是签名方法的调用点,而execution捕获的则是执行点。

call和execution的语法

within()的参数是一个类,比如我们可以通过within(A.class)或者!within(A.class)来过滤想要拦截的点

withincode()和within()相似,只不过withincode()接收的参数是方法的signature

target()判断目标对象是否是某种类型,this()判断当前执行对象是否是某种类型

举例:

execution (* com.mjt..*.*(..))

1、execution(): 表达式主体。

2、第一个*号:表示返回类型,*号表示所有的类型。

3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.mjtl包、子孙包下所有类的方法。

4、第二个*号:表示类名,*号表示所有的类。

5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

@Before在拦截点或者调用点之前调用

@After是在拦截点或者调用点之后调用

被@Around注解的方法,会被织入到拦截方法调用点或这行点之前,

接着我运行项目,编译通过后,快速的点击了一个按钮

log日志显示

2019-05-08 11:43:41.717 14103-14103/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.adapter.ProductAdapter.1.onClick(View))

2019-05-08 11:43:41.718 14103-14103/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕获到了,isFastClick=false

2019-05-08 11:43:41.718 14103-14103/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.adapter.ProductAdapter.1.onClick(View))

2019-05-08 11:43:41.883 14103-14103/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.adapter.ProductAdapter.1.onClick(View))

2019-05-08 11:43:41.883 14103-14103/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕获到了,isFastClick=true

执行了,before方法,然后进入被@Around修饰的方法,看到第一次点击判断不是快速点击

放过拦截,proceedingJoinPoint.proceed();原方法得到执行!

第二次点击,判断是快速点击,proceedingJoinPoint.proceed()没有执行,也就是原方法被拦截掉了!

但是这时候我发现如果点击事件是使用lambda表达式是无法拦截的,因为这里的pointcut的execution是这样

@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")

public void dealWithNormal() {

}

这个正则的大致意思是,拦截返回类型为void, android.view.View.OnClickListener.onClick()方法,参数(..)表示参数可以是任意数量任意类型

那么接着写pointcut拦截lambda表达式的点击事件

于是AspectHandler 切面类被我改成了这样

package com.mjt.pad.common.aspect;

import android.util.Log;

import com.mjt.common.utils.UIUtils;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

/**

* Copyright:mjt_pad_android

* Author: liyang

* Date:2019-05-05 15:41

* Desc:

*/

@Aspect

public class AspectHandler {

private static final String TAG = AspectHandler.class.getSimpleName();

@Before("dealWithNormal()||dealWithLambda()")

public void beforePoint(JoinPoint joinPoint) {

Log.e(TAG, "before: " + joinPoint);

}

@Pointcut("execution(void com.mjt..lambda*(android.view.View))")

public void dealWithLambda() {

}

@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")

public void dealWithNormal() {

}

@Around("dealWithNormal()||dealWithLambda()")

public void onViewClicked(ProceedingJoinPoint proceedingJoinPoint) {

boolean isFastClickPassed = !UIUtils.isFastClickOnlyInAspect();

Log.e(TAG, "onViewClicked: 捕获到了,isFastClick=" + !isFastClickPassed);

if (isFastClickPassed) {

Log.e(TAG, "onViewClicked: " + proceedingJoinPoint);

try {

proceedingJoinPoint.proceed();

} catch (Throwable throwable) {

throwable.printStackTrace();

Log.e(TAG, "onViewClicked: ", throwable);

}

}

}

}

增加的pointcut对点击事件采用lambda表达式的拦截

然后我快速的点击了一个采用lambda表达式方式实现的点击事件log日志如下

2019-05-08 11:55:05.506 15052-15052/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.fragment.print.PrintManagerFragment.lambda$initViews$1(View))

2019-05-08 11:55:05.506 15052-15052/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕获到了,isFastClick=false

2019-05-08 11:55:05.506 15052-15052/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.fragment.print.PrintManagerFragment.lambda$initViews$1(View))

2019-05-08 11:55:05.755 15052-15052/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.fragment.print.PrintManagerFragment.lambda$initViews$1(View))

2019-05-08 11:55:05.755 15052-15052/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕获到了,isFastClick=true

嗯,lambda方式实现的点击事件也被拦截到了

接下来,如果某个小伙伴或我这个按钮不要拦截快速点击,那怎么办呢?

嗯,采用自定义注解,如果某个onClick方法请求放过快速点击拦截,加上这个注解就好了

接着我们写一个自定义注解就叫Ignore

作用于CLASS,修饰的目标为方法和构造函数

package com.mjt.pad.common.aspect;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* Copyright:mjt_pad_android

* Author: liyang

* Date:2019-05-05 16:35

* Desc:

*/

@Retention(RetentionPolicy.CLASS)

@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})

public @interface Ignore {

}

接着改动AspectHandler切面类

package com.mjt.pad.common.aspect;

import android.util.Log;

import com.mjt.common.utils.UIUtils;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

/**

* Copyright:mjt_pad_android

* Author: liyang

* Date:2019-05-05 15:41

* Desc:

*/

@Aspect

public class AspectHandler {

private static final String TAG = AspectHandler.class.getSimpleName();

private volatile boolean isIgnored = false;

@Before("execution(@com.mjt.pad.common.aspect.Ignore void com.mjt..*.onClick(..))")

public void checkIgnore(JoinPoint joinPoint) {

isIgnored = true;

Log.e(TAG, "checkIgnore: " + joinPoint);

}

@Pointcut("execution(void com.mjt..lambda*(android.view.View))")

public void dealWithLambda() {

}

@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")

public void dealWithNormal() {

}

@Around("dealWithNormal()||dealWithLambda()")

public void onViewClicked(ProceedingJoinPoint proceedingJoinPoint) {

boolean isFastClickPassed = !UIUtils.isFastClickOnlyInAspect();

Log.e(TAG, "onViewClicked: 捕获到了,isFastClick=" + !isFastClickPassed+",isIgnored="+isIgnored);

if (isIgnored||isFastClickPassed) {

Log.e(TAG, "onViewClicked: " + proceedingJoinPoint);

try {

proceedingJoinPoint.proceed();

isIgnored=false;

} catch (Throwable throwable) {

throwable.printStackTrace();

Log.e(TAG, "onViewClicked: ", throwable);

}

}

}

}

对@Before方法进行了修改

@Before("execution(@com.mjt.pad.common.aspect.Ignore void com.mjt..*.onClick(..))")

public void checkIgnore(JoinPoint joinPoint) {

isIgnored = true;

Log.e(TAG, "checkIgnore: " + joinPoint);

}

这"execution(@com.mjt.pad.common.aspect.Ignore void com.mjt..*.onClick(..))"匹配的是被我们自定义注解@Ignore修饰的 com.mjt包及其子包下的所有onClick方法

然后我们找一个onClick方法加上@Ignore注解看看起作用没

@Ignore

@Override

public void onClick(View v) {

switch (v.getId()){

case R.id.llyPart:

...

接着找到这个被Ignore修饰的点击事件,快速点击两下

log日志如下

2019-05-08 12:19:06.925 16912-16912/com.mjt.pad.test E/AspectHandler: checkIgnore: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))

2019-05-08 12:19:06.925 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕获到了,isFastClick=false,isIgnored=true

2019-05-08 12:19:06.926 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))

2019-05-08 12:19:07.086 16912-16912/com.mjt.pad.test E/AspectHandler: checkIgnore: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))

2019-05-08 12:19:07.087 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕获到了,isFastClick=true,isIgnored=true

2019-05-08 12:19:07.087 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))

可以看到,是快速点击,但是原方法也得到了执行

嗯 对项目的全局处理点击事件大致就是这样,aspectj相关的东西有很多

关于aspectj的具体使用请查看 aspectj官方文档

invoke 按钮点击_使用aspectj对app中按钮的快速点击进行处理相关推荐

  1. 为什么博图中放置按下按钮无反应_如何更好的设计按钮

    按钮是交互设计的常见元素.虽然它们看起来像是一个非常简单的UI元素,但它们仍然是最重要的创建元素之一. 在今天的文章中,将介绍大家需要了解的基本项目,以便创建有效的控件来改善用户体验. 使按钮看起来像 ...

  2. $.ligerdialog.open中确定按钮加事件_彻底搞懂JavaScript中的this指向问题

    JavaScript中的this是让很多开发者头疼的地方,而this关键字又是一个非常重要的语法点.毫不夸张地说,不理解它的含义,大部分开发任务都无法完成. 想要理解this,你可以先记住以下两点: ...

  3. wps启用编辑按钮在哪里_如何在wps工具栏中添加按钮 如何在Excel中添加删除命令按钮...

    延伸:如何在Excel中添加删除命令按钮 描述:在Excel 2003中,很多常用的命令按钮都放置在工具栏中,用户可以很方便地进行操作.添加命令主要是通过拖动的方式完成的,其具体的操作如下:骤一,在视 ...

  4. 如何调整按钮里的文字的位置android_UI设计中按钮场景分析

    别无他话,今天就和大家聊一聊设计宿敌--按钮设计.按钮的设计看似简单,其实按钮的功能不同,设计形式上也会有差异,今天我就针对按钮的场景分析,对按钮进行全面的解析. 按照功能性分类,按钮主要包括行为召唤 ...

  5. 按钮点击_如何设置微信小程序按钮点击事件?

    当我们需要点击按钮实现一个操作时,我们应该怎么设置?这里给大家介绍两种方法: 方法一:使用小程序模板,无需懂技术. 这种方法比较简单,你只需要选一个比较简单的小程序模板,比如「上线了」sxl.cn,在 ...

  6. java如何避免恶意连续点击_浅谈一下如何避免用户多次点击造成的多次请求

    浅谈一下如何避免用户多次点击造成的多次请求 一.有效地在web客户端采用一定机制去防止重复点击提交,将大大减轻服务器端压力. 1> 定义标志位: 点击触发请求后,标志位为false量:请求(或者 ...

  7. wps启用编辑按钮在哪里_电脑WPS文字编辑中即点即输功能的启动步骤

    即点即输是Microsoft Office 中WORD的一项功能,是指鼠标指针指向需要编辑的文字位置,单击鼠标即可进行文字输入.那么,WPS文字编辑软件中"即点即输"功能怎么启用呢 ...

  8. cocos 禁掉快速点击_如何用PAM2500叶绿素荧光仪测量快速光曲线

    除了慢速荧光诱导动力学曲线以外,PAM-2500另一个常用功能就是测量快速光曲线(Light Curve). 何为快速光曲线,它也是一个相对的概念,与之对应的是用光合仪测量的光响应曲线(A/Q Cur ...

  9. android 按钮并排左右_线性布局有两个按钮并排 - 安卓

    我有3个按钮下出现低于对方的,其中layout.xml ...线性布局有两个按钮并排 - 安卓 android:layout_width="match_parent" androi ...

最新文章

  1. 第五周周记(国庆第一天)
  2. eclipse color theme 主题颜色
  3. 27、Power Query-日期与时间数据处理实例(图书室借书统计)
  4. 【NLP】图解 BERT 预训练模型!
  5. Node.js 入门详解(四)
  6. Linux 查看网段内所有IP
  7. 7.1 elementui的radio无法选中问题
  8. springboot中通过cors协议解决跨域问题
  9. 无废话WPF系列16:资源
  10. linux ftp下载geo,GEO数据下载及处理详细过程
  11. 自动机器学习简述(AutoML)
  12. 《熊出没原始时代》总导演丁亮:爱与勇气 穿越古今
  13. 写给青春,写给军乐团
  14. JavaSE总结知识点 重要的点(并不是详细的教材语录)
  15. 虚拟服务器鼠标左键被锁了,鼠标在网页里左键被锁怎么办
  16. 怎么降低软件开发成本风险_降低开发人员成本的5种方法
  17. 立创EDA之导出BOM
  18. springboot配置datasource连接与mapper.xml文件位置
  19. 【NXP出品PINS-TOOL-IMX】IMX6ULL引脚配置工具PINS-TOOL-IMX介绍
  20. SPM12入门案例1

热门文章

  1. PCI DSS合规性审核认证服务
  2. IntelliJ IDEA / Webstorm 2019.3 PJ方法(永久有效)
  3. 【Windows下设置全局以管理员身份运行cmd】
  4. 现在香港开银行账户又变难了
  5. 计算机多媒体课件设计,多媒体课件设计与制作 教师课件制作平台
  6. 怎么把Word转换成PDF?这几种转换神器分享给你
  7. php外边距的代码,外边距简写属性 margin
  8. H3C新版本模拟器HCL_Setup_V3.0.1安装流程
  9. 中国的人工智能是否能在2030年引领世界?
  10. JAVA实现的百万英雄,芝士超人等答题,自动搜索答案,答题辅助