美文网首页
AOP-AspectJ 实现防重复点击

AOP-AspectJ 实现防重复点击

作者: 一个追寻者的故事 | 来源:发表于2020-04-10 16:41 被阅读0次

    一、概念介绍

    我们要实现项目中点击事件的防重复点击功能,AOP思想可以很好的实践。业务代码和其它代码可以完全解耦。 使用 aspectJ 来完成这一次的实践。

    二、实践

    2.1 环境配置
    2.1.1 Gradle配置(复杂版)

    项目根目录build.gradle进行配置,添加mavenCentral()仓库,并配置 aspectjtoolsaspectjweaver 的版本

    buildscript {
        ext.kotlin_version = '1.3.50'
        repositories {
            google()
            jcenter()
            mavenCentral()
            
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.5.1'
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
            classpath 'org.aspectj:aspectjtools:1.9.1'
            classpath 'org.aspectj:aspectjweaver:1.9.1'
        }
    }
    
    allprojects {
        repositories {
            google()
            jcenter()
            
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    再将aspectJ的配置拷贝到根目录build.gradle,在这里暴露一个模块结构类型的入参

    /**
     *  AspectJ 的配置
     */
    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    
    def aop(variants) {
        def log = project.logger
        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
                    }
                }
            }
        }
    }
    

    再前往需要的模块去配置,主模块的build.gradle:

    //aspectJ 配置
    rootProject.aop(project.android.applicationVariants)
    

    同时 dependencies里添加 aspectjrt版本 'org.aspectj:aspectjrt:1.9.1'

    //aspectJ 配置
    implementation 'org.aspectj:aspectjrt:1.9.1'
    

    如果要在其它Moudle的 build.gradle 里进行配置,如下:

    rootProject.aop(project.android.libraryVariants)
    

    然后在dependencies里再添加一遍。

    2.1.1 Gradle配置(精简版)

    如果使用AspectJ,需要在项目的build里面进行一大丢配置,这里为了方便快捷,推荐使用沪江的gradle_plugin_android_aspectjx。(原理是:通过Gradle Plugin的方式 将公共的功能可以抽取出来成为插件,可以供多个 Module (build.gradle)使用,增加复用性。 其中包括 aspectJ 复杂的gradle配置,都集成在里边了)

    1、按照github使用指南,添加依赖。

    根目录的build.gradle

    buildscript {
        ...
        dependencies {
            classpath 'com.android.tools.build:gradle:3.0.1'
            //添加依赖,如果studio是3.0以上版本,建议使用v1.1.1
            classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.1.1'
            //注意不能少了aspectjtools的依赖,否则会报错
            //同样aspectjtools可以不写在这里,写在app主module的dependencies下面。
        }
    }
    
    allprojects {
        ...
    }
    

    app 主目录中的build.gradle

    apply plugin: 'com.hujiang.android-aspectjx'
    
    android {
        ...
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        ...
        //aspectjrt的依赖
        implementation 'org.aspectj:aspectjrt:1.8.13'
    }
    
    
    2.2 业务实现
    2.2.1 定义注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface SingleClick {
        long value() default 200;
    }
    
    2.2.2 定义切面
    @Aspect
    public class SingleClickAspect {
    
        int lastViewId = -1;
        long lastClickTimestamp = 0;
    
        //切入点:规则
        @Pointcut("execution(@com.jxf.aspectjdemo1.annotation.SingleClick * *(..))")
        public void clickFun(){ }
    
        // 连接点
        @Around("clickFun()")
        public void handleClick(ProceedingJoinPoint joinPoint) throws Throwable{
    
            //取click方法中的参数
            if(joinPoint.getArgs() != null
                    && joinPoint.getArgs().length > 0
                    && joinPoint.getArgs()[0] instanceof View){
    
                View target = (View) joinPoint.getArgs()[0];
    
                //签名信息
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                Method method = methodSignature.getMethod();
    
                if (method.isAnnotationPresent(SingleClick.class)){
                    SingleClick singleClickAnnotation = method.getAnnotation(SingleClick.class);
                    long nowTime = System.currentTimeMillis();
                    if(nowTime - lastClickTimestamp <= singleClickAnnotation.value()
                            && target.getId() == lastViewId){
                        Log.i("AOP", "you click is too fast!");
                    }else{
                        // 记住上一次点击的时间戳和View的ID
                        lastViewId = target.getId();
                        lastClickTimestamp = nowTime;
    
                        //执行click方法
                        joinPoint.proceed();
                    }
                }
            }
    
        }
    }
    
    
    2.2.3 调用
    public class MainActivity extends AppCompatActivity {
    
        int number = 1;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @SingleClick
        public void txtClick(View v){
            Log.d("AOP", "----------------"+number++);
        }
    }
    
    
    2.2.4 结果
    执行结果
    2.2.4 其它

    我们看一下 编译过后的 MainActivity.class 的内容

    public class MainActivity extends AppCompatActivity {
        int number = 1;
    
        public MainActivity() {
        }
    
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            this.setContentView(2131361820);
        }
    
        @SingleClick
        public void txtClick(View v) {
            JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, v);
            txtClick_aroundBody1$advice(this, v, var3, SingleClickAspect.aspectOf(), (ProceedingJoinPoint)var3);
        }
    
        static {
            ajc$preClinit();
        }
    }
    
    

    生成的class文件可以看出,编译生成字节码文件时,可以通过aspectJ 的编译器生成符合java字节码规范的class文件,从而可以达到灵活插入部分代码(Aspect)的目的。

    三、其它

    其它实现:

    方法调用性能检测 : hugo
    Android 权限申请 :android_permission_aspectjx

    参考:
    https://www.jianshu.com/p/f577aec99e17
    https://www.jianshu.com/p/16c5e8cdcf08

    AspectJ官网
    AspectJ Programming Guide
    AspectJ Development Environment Guide
    AspectJ NoteBook

    相关文章

      网友评论

          本文标题:AOP-AspectJ 实现防重复点击

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