背景
Android 点击一个 View 跳到个页面这个常规事件,有时候用户会用他们单身几十年的手速1秒内戳了那么三四五六七八下,然后这个时候要是用户稍微手机性能差点,一下子就跳出那么两三个同样的 Activity 出来,这种去情况还是挺尴尬的,在这里我们可以用面向切面(AOP)的知识对我们这么一个重复事件进行拦截处理。
AspectJX
AspectJX 是个啥子东西来的?GitHub 上是这么说的
一个基于AspectJ并在此基础上扩展出来可应用于Android开发平台的AOP框架,可作用于java源码,class文件及jar包,同时支持kotlin的应用。
- 插件引用
在项目根目录的 build.gradle 里依赖 AspectJX
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
}
在 app Module 的 build.gradle 里加入依赖
apply plugin: 'com.android.application'
apply plugin: 'com.jakewharton.butterknife'
apply plugin: 'android-aspectjx'//这一行 别加错地方了
android {
...
}
好了 Sync 一下吧 ~
配置有问题就看一下官网吧亲~
顺道说一下我踩到的坑,AspectJX 和阿里推送以及某些推送 SDK 冲突问题。以下是我 Google 到的解决方法。
在当前的 module 的 gradle 文件加入如下
android {
//.... 省略
}
dependencies {
//.... 省略
}
//aspectj AOP 和 阿里推送的冲突问题
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
拦截器
我们的拦截器要起到以下作用
- 在一定的时间内(可设置值)的重复点击事件的拦截,针对同一个 view
- 要求在某些情况下不需要拦截此点击事件(例如自定义view 的时候)
以下内容需要一点 AspectJX 和 AOP 的知识,如果无法理解建议先了解上面两个知识点。
首先写下我们的拦截事件
@Aspect
public class InterceptAgainClickAOP {
private final String TAG = this.getClass().getSimpleName();
//上次点击的时间
private static Long sLastclick = 0L;
//拦截所有两次点击时间间隔小于一秒的点击事件
private static final Long FILTER_TIMEM = 1000L;
//上次点击事件View
private View lastView;
//拦截所有* android.view.View.OnClickListener.onClick(..) 事件
//直接setOnclikListener 拦截点击事件
@Around("execution(* android.view.View.OnClickListener.onClick(..))")
public void clickFilterHook(ProceedingJoinPoint joinPoint) throws Throwable {
//大于指定时间
if (System.currentTimeMillis() - sLastclick >= FILTER_TIMEM) {
doClick(joinPoint);
} else {
//小于指定秒数 但是不是同一个view 可以点击 或者不过滤点击
if (lastView == null || lastView != (joinPoint).getArgs()[0]) {
doClick(joinPoint);
} else {
//大于指定秒数 且是同一个view
Log.e(TAG, "重复点击,已过滤");
}
}
}
//执行原有的 onClick 方法
private void doClick(ProceedingJoinPoint joinPoint) throws Throwable {
//判断 view 是否存在
if (joinPoint.getArgs().length == 0) {
joinPoint.proceed();
return;
}
//记录点击的 view(最好加上捕获类型转换异常的try)
lastView = (View) (joinPoint).getArgs()[0];
//记录点击事件
sLastclick = System.currentTimeMillis();
//执行点击事件
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
这个时候已经满足我们上述的第一个要求 : **在一定的时间内(可设置值)的重复点击事件的拦截,针对同一个 view **
接下来我们写一个注解
/**
* 标记不需要拦截点击
*
* @author Cassie
* @date 2019年9月26日
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface UncheckClick {
}
我们利用这个注解去标记不需要过滤的点击事件修改上面 InterceptAgainClickAOP.java 的代码
@Aspect
public class InterceptAgainClickAOP {
private final String TAG = this.getClass().getSimpleName();
//上次点击的时间
private static Long sLastclick = 0L;
//拦截所有两次点击时间间隔小于一秒的点击事件
private static final Long FILTER_TIMEM = 1000L;
//上次点击事件View
private View lastView;
//---- add content -----
//是否过滤点击 默认是
private boolean checkClick = true;
//---- add content -----
//拦截所有* android.view.View.OnClickListener.onClick(..) 事件
//直接setOnclikListener 拦截点击事件
@Around("execution(* android.view.View.OnClickListener.onClick(..))")
public void clickFilterHook(ProceedingJoinPoint joinPoint) throws Throwable {
//大于指定时间
if (System.currentTimeMillis() - sLastclick >= FILTER_TIMEM) {
doClick(joinPoint);
} else {
//---- update content ----- 判断是否需要过滤点击
//小于指定秒数 但是不是同一个view 可以点击 或者不过滤点击
if (!checkClick ||lastView == null || lastView != (joinPoint).getArgs()[0]) {
//---- update content ----- 判断是否需要过滤点击
doClick(joinPoint);
} else {
//大于指定秒数 且是同一个view
Log.e(TAG, "重复点击,已过滤");
}
}
}
//执行原有的 onClick 方法
private void doClick(ProceedingJoinPoint joinPoint) throws Throwable {
//判断 view 是否存在
if (joinPoint.getArgs().length == 0) {
joinPoint.proceed();
return;
}
//记录点击的 view
lastView = (View) (joinPoint).getArgs()[0];
//---- add content -----
//修改默认过滤点击
checkClick = true;
//---- add content -----
//记录点击事件
sLastclick = System.currentTimeMillis();
//执行点击事件
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
//标志不过滤点击
@Before("execution(@com.cassie.annotation.aop.UncheckClick * *(..))")
public void beforeuncheckClcik(JoinPoint joinPoint) throws Throwable {
Log.i(TAG, "beforeuncheckClcik");
//修改为不需要过滤点击
checkClick = false;
}
}
以上就是整个过滤重复点击的 AOP 了,注释应该清楚了就不赘述啦~