apt案例之点击事件

yc211 yc211     2023-03-09     559

关键词:

目录介绍

  • 01.创建项目步骤
    • 1.1 项目搭建
    • 1.2 项目功能
  • 02.自定义注解
  • 03.创建Processor
  • 04.compiler配置文件
  • 05.编译jar
  • 06.如何使用
  • 07.编译生成代码
  • 08.部分源码说明
    • 8.1 Process类-process方法
    • 8.2 OnceProxyInfo代理类
    • 8.3 OnceMethod类

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!

关于apt实践与总结开源库地址

https://github.com/yangchong211/YCApt

00.注解系列博客汇总

0.1 注解基础系列博客

  • 01.Annotation注解详细介绍
  • 02.Dagger2深入分析,待更新
  • 03.注解详细介绍
    • 什么是注解,注解分类有哪些?自定义注解分类?运行注解案例展示分析,以一个最简单的案例理解注解……使用注解替代枚举,使用注解限定类型
  • 04.APT技术详解
    • 什么是apt?理解注解处理器的作用和用途……android-apt被替代?annotationProcessor和apt区别? 什么是jack编译方式?
  • 06.自定义annotation注解
    • @Retention的作用?@Target(ElementType.TYPE)的解释,@Inherited注解可以被继承吗?Annotation里面的方法为何不能是private?
  • 07.注解之兼容kotlin
    • 后期更新
  • 08.注解之处理器类Processor
    • 处理器类Processor介绍,重要方法,Element的作用,修饰方法的注解和ExecutableElement,了解修饰属性、类成员的注解和VariableElement……
  • 10.注解遇到问题和解决方案
    • 无法引入javax包下的类库,成功运行一次,修改代码后再运行就报错
  • 11.注解代替枚举
    • 在做内存优化时,推荐使用注解代替枚举,因为枚举占用的内存更高,如何说明枚举占用内存高呢?这是为什么呢?
  • 12.注解练习案例开源代码
    • 注解学习小案例,比较系统性学习注解并且应用实践。简单应用了运行期注解,通过注解实现了setContentView功能;简单应用了编译器注解,通过注解实现了防暴力点击的功能,同时支持设置时间间隔;使用注解替代枚举;使用注解一步步搭建简单路由案例。结合相应的博客,在来一些小案例,从此应该对注解有更加深入的理解……
  • 13 ARouter路由解析
    • 比较详细地分析了阿里路由库
  • 14 搭建路由条件
    • 为何需要路由?实现路由方式有哪些,这些方式各有何优缺点?使用注解实现路由需要具备的条件以及简单原理分析……
  • 15 通过注解去实现路由跳转
    • 自定义Router注解,Router注解里有path和group,这便是仿照ARouter对路由进行分组。然后看看注解生成的代码,手写路由跳转代码。
  • 16 自定义路由Processor编译器
    • Processor介绍,重要方法,Element的作用,修饰方法的注解和ExecutableElement
  • 17 利用apt生成路由映射文件
    • 在Activity类上加上@Router注解之后,便可通过apt来生成对应的路由表,那么究竟是如何生成的代码呢?
    • 在组件化开发中,有多个module,为何要在build.gradle配置moduleName,又是如何通过代码拿到module名称?
    • process处理方法如何生成代码的,又是如何写入具体的路径,写入文件的?
    • 看完这篇文章,应该就能够理解上面这些问题呢!
  • 18 路由框架的设计和初始化
    • 编译期是在你的项目编译的时候,这个时候还没有开始打包,也就是你没有生成apk呢!路由框架在这个时期根据注解去扫描所有文件,然后生成路由映射文件。这些文件都会统一打包到apk里,app运行时期做的东西也不少,但总而言之都是对映射信息的处理,如执行执行路由跳转等。那么如何设计框架呢?
    • 生成的注解代码,又是如何把这些路由映射关系拿到手,或者说在什么时候拿到手比较合适?为何注解需要进行初始化操作?
    • 如何得到得到路由表的类名,如何得到所有的routerAddress---activityClass映射关系?
  • 19 路由框架设计注意要点
    • 需要注意哪些要点?
  • 20 为何需要依赖注入
    • 有哪些注入的方式可以解耦,你能想到多少?路由框架为何需要依赖注入?路由为何用注解进行依赖注入,而不是用反射方式注入,或者通过构造方法注入,或者通过接口方式注入?
  • 21 Activity属性注入
    • 在跳转页面时,如何传递intent参数,或者如何实现跳转回调处理逻辑?

01.创建项目步骤

1.1 项目搭建

  • 首先创建一个Android项目。然后给我们的项目增加一个module,一定要记得是Java Library。因为APT需要用到jdk下的 【 *javax.~ *】包下的类,这在AndroidSdk中是没有的。
  • 一定要注意:**需要说明的是:**我们的目的是写一个Android库,APT Moudle是java Library,不能使用Android API。所以还需要创建一个Android Library,负责框架主体部分. 然后由Android Library引用APT jar包。
  • 项目目录结构如图:
    • app:Demo
    • AptAnnotation:java Library主要放一些项目中需要用到的自定义注解及相关代码
    • AptApi:Android Library. OnceClick是我们真正对外发布并交由第三方使用的库,它引用了apt-jar包
    • AptCompiler:java Library主要是应用apt技术处理注解,生成相关代码或者相关源文件,是核心所在。

1.2 项目功能

  • 在一定时间内,按钮点击事件只能执行一次。未到指定时间,不执行点击事件。

02.自定义注解

  • 创建Annotation Module,需要创建一个Java Library,名称可为annotation,主要放一些项目中需要用到的自定义注解及相关代码
  • 新建一个类,OnceClick。就是我们自定义的注解。
    /**
     * <pre>
     *     @author 杨充
     *     blog  : https://github.com/yangchong211
     *     time  : 2017/06/21
     *     desc  : 一定time时间内该点击事件只能执行一次
     *     revise:
     * </pre>
     */
    //@Retention用来修饰这是一个什么类型的注解。这里表示该注解是一个编译时注解。
    @Retention(RetentionPolicy.CLASS)
    //@Target用来表示这个注解可以使用在哪些地方。
    // 比如:类、方法、属性、接口等等。这里ElementType.METHOD 表示这个注解可以用来修饰:方法
    @Target(ElementType.METHOD)
    //这里的interface并不是说OnceClick是一个接口。就像申明类用关键字class。申明注解用的就是@interface。
    public @interface OnceClick 
        //返回值表示这个注解里可以存放什么类型值
        int value();
    
    

03.创建Processor

  • 创建Compiler Module,需要再创建一个Java Library,名称可为compiler,主要是应用apt技术处理注解,生成相关代码或者相关源文件,是核心所在。
  • Processor是用来处理Annotation的类。继承自AbstractProcessor。
    /**
     * <pre>
     *     @author 杨充
     *     blog  : https://github.com/yangchong211
     *     time  : 2017/06/21
     *     desc  : 自定义Processor编译器
     *     revise:
     * </pre>
     */
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class OnceClickProcessor extends AbstractProcessor 
    
        private Messager messager;
        private Elements elementUtils;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) 
            super.init(processingEnv);
            messager = processingEnv.getMessager();
            elementUtils = processingEnv.getElementUtils();
        
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 
            //获取proxyMap
            Map<String, OnceProxyInfo> proxyMap = getProxyMap(roundEnv);
            //遍历proxyMap,并生成代码
            for (String key : proxyMap.keySet()) 
                OnceProxyInfo proxyInfo = proxyMap.get(key);
                writeCode(proxyInfo);
            
            return true;
        
    
        @Override
        public Set<String> getSupportedAnnotationTypes() 
            Set<String> types = new LinkedHashSet<>();
            types.add(OnceClick.class.getCanonicalName());
            return types;
        
    
        @Override
        public SourceVersion getSupportedSourceVersion() 
            return super.getSupportedSourceVersion();
        
    
    

04.compiler配置文件

  • build.gradle文件配置
    • auto-service的作用是向系统注册processor(自定义注解处理器),执行编译时使用processor进行处理。
    • javapoet提供了一套生成java代码的api,利用这些api处理注解,生成新的代码或源文件。
    • OnceClickAnnotation是上文创建的注解module。
    apply plugin: ‘java-library‘
    
    dependencies 
        implementation fileTree(dir: ‘libs‘, include: [‘*.jar‘])
        implementation ‘com.google.auto.service:auto-service:1.0-rc3‘
        implementation ‘com.squareup:javapoet:1.10.0‘
        implementation project(‘:OnceClickAnnotation‘)
    
    
    sourceCompatibility = "7"
    targetCompatibility = "7"
    

05.编译jar

  • 这里有一个坑,主Module是不可以直接引用这个java Module的。(直接引用,可以成功运行一次~修改代码以后就不能运行了)而如何单独编译这个java Module呢?在编译器Gradle视图里,找到Module apt下的build目录下的Build按钮。双击运行。
    • 代码没有问题编译通过的话,会有BUILD SUCCESS提示。生成的jar包在 apt 下的build目录下的libs下。将apt.jar拷贝到app下的libs目录,右键该jar,点击Add as Library,添加Library

06.如何使用

  • 代码如下所示
    public class MainActivity extends AppCompatActivity 
    
        @Override
        protected void onCreate(Bundle savedInstanceState) 
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //初始化OnceClick,并设置点击事件间隔是2秒
            OnceInit.once(this,2000);
        
    
        @OnceClick(R.id.tv_1)
        public void Click1()
            Log.d("tag--------------------","tv_1");
        
    
        @OnceClick(R.id.tv_2)
        public void Click2(View v)
            Log.d("tag--------------------","tv_2");
        
    
    

07.编译生成代码

  • 编译之后生成的代码路径,在项目中的build文件夹,如图所示
    • 技术图片
  • 编译之后生成的代码
    // 编译生成的代码,不要修改
    // 更多内容:https://github.com/yangchong211
    package com.ycbjie.ycapt;
    
    import android.view.View;
    import com.ycbjie.api.Finder;
    import com.ycbjie.api.AbstractInjector;
    
    public class MainActivity$$_Once_Proxy<T extends MainActivity> implements AbstractInjector<T> 
    
        public long intervalTime; 
    
        @Override 
        public void setIntervalTime(long time) 
            intervalTime = time;
         
    
        @Override 
        public void inject(final Finder finder, final T target, Object source) 
            View view;
            view = finder.findViewById(source, 2131165325);
            if(view != null)
                view.setOnClickListener(new View.OnClickListener() 
                long time = 0L;
                @Override
                public void onClick(View v) 
                    long temp = System.currentTimeMillis();
                    if (temp - time >= intervalTime) 
                        time = temp;
                        target.Click1();
                    
                );
            
            view = finder.findViewById(source, 2131165326);
            if(view != null)
                view.setOnClickListener(new View.OnClickListener() 
                long time = 0L;
                @Override
                public void onClick(View v) 
                    long temp = System.currentTimeMillis();
                    if (temp - time >= intervalTime) 
                        time = temp;
                        target.Click2(v);
                    
                );
            
      
    
    
    

08.部分源码说明

8.1 Process类-process方法

  • 当某个类Activity使用了@OnceClick注解之后,我们就应该为其生成一个对应的代理类,代理类实现我们框架的功能:为某个View设置点击事件,并且这个点击事件一定时间内只能执行一次。所以,一个代理类可能有多个需要处理的View。
  • 先看process代码:
    • ProxyInfo对象:存放生成代理类的必要信息,并生成代码。
    • getProxyMap方法:使用参数roundEnv,遍历所有@OnceClick注解,并生成代理类ProxyInfo的Map。
    • writeCode方法:真正生成代码的方法。
    • 总结一下:编译时,取得所有需要生成的代理类信息。遍历代理类集合,根据代理类信息,生成代码。
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 
        //获取proxyMap
        Map<String, OnceProxyInfo> proxyMap = getProxyMap(roundEnv);
        //遍历proxyMap,并生成代码
        for (String key : proxyMap.keySet()) 
            OnceProxyInfo proxyInfo = proxyMap.get(key);
            //写入代码
            writeCode(proxyInfo);
        
        return true;
    
    

8.2 OnceProxyInfo代理类

  • 其实这个类,才是这个框架的重中之重,因为生成什么代码,全靠这个类说了算。这个类也没什么好讲的,就是用StringBuidler拼出一个类来。ProxyInfo保存的是类信息,方法信息我们用List methods保存。然后根据这些信息生成类。
    public class OnceProxyInfo 
        
        private String packageName;
        private String targetClassName;
        private String proxyClassName;
        private TypeElement typeElement;
        private List<OnceMethod> methods;
        private static final String PROXY = "_Once_Proxy";
    
        OnceProxyInfo(String packageName, String className) 
            this.packageName = packageName;
            this.targetClassName = className;
            this.proxyClassName = className + "$$" + PROXY;
        
    
        String getProxyClassFullName() 
            return packageName + "." + proxyClassName;
        
    
        String generateJavaCode() throws OnceClickException 
    
            StringBuilder builder = new StringBuilder();
            builder.append("// 编译生成的代码,不要修改
    ");
            builder.append("// 更多内容:https://github.com/yangchong211
    ");
            builder.append("package ").append(packageName).append(";
    
    ");
    
            //写入导包
            builder.append("import android.view.View;
    ");
            builder.append("import com.ycbjie.api.Finder;
    ");
            builder.append("import com.ycbjie.api.AbstractInjector;
    ");
            builder.append(‘
    ‘);
    
            builder.append("public class ").append(proxyClassName)
                    .append("<T extends ").append(getTargetClassName()).append(">")
                    .append(" implements AbstractInjector<T>").append(" 
    ");
            builder.append(‘
    ‘);
    
            generateInjectMethod(builder);
            builder.append(‘
    ‘);
    
            builder.append("
    ");
            return builder.toString();
    
        
    
        private String getTargetClassName() 
            return targetClassName.replace("$", ".");
        
    
        private void generateInjectMethod(StringBuilder builder) throws OnceClickException 
            builder.append("    public long intervalTime; 
    ");
            builder.append(‘
    ‘);
    
            builder.append("    @Override 
    ")
                    .append("    public void setIntervalTime(long time) 
    ")
                    .append("        intervalTime = time;
         
    ");
            builder.append(‘
    ‘);
    
            builder.append("    @Override 
    ")
                    .append("    public void inject(final Finder finder, final T target, Object source) 
    ");
            builder.append("        View view;");
            builder.append(‘
    ‘);
    
            //这一步是遍历所有的方法
            for (OnceMethod method : getMethods()) 
                builder.append("        view = ")
                        .append("finder.findViewById(source, ")
                        .append(method.getId())
                        .append(");
    ");
                builder.append("        if(view != null)
    ")
                        .append("            view.setOnClickListener(new View.OnClickListener() 
    ")
                        .append("            long time = 0L;
    ");
                builder.append("            @Override
    ")
                        .append("            public void onClick(View v) 
    ");
                builder.append("                long temp = System.currentTimeMillis();
    ")
                        .append("                if (temp - time >= intervalTime) 
    " +
                                "                    time = temp;
    ");
                if (method.getMethodParametersSize() == 1) 
                    if (method.getMethodParameters().get(0).equals("android.view.View")) 
                        builder.append("                    target.")
                                .append(method.getMethodName()).append("(v);");
                     else 
                        throw new OnceClickException("Parameters must be android.view.View");
                    
                 else if (method.getMethodParametersSize() == 0) 
                    builder.append("                    target.")
                            .append(method.getMethodName()).append("();");
                 else 
                    throw new OnceClickException("Does not support more than one parameter");
                
                builder.append("
                    
    ")
                        .append("            ")
                        .append(");
            
    ");
            
    
            builder.append("  
    ");
        
    
        TypeElement getTypeElement() 
            return typeElement;
        
    
        void setTypeElement(TypeElement typeElement) 
            this.typeElement = typeElement;
        
    
        List<OnceMethod> getMethods() 
            return methods == null ? new ArrayList<OnceMethod>() : methods;
        
    
        void addMethod(OnceMethod onceMethod) 
            if (methods == null) 
                methods = new ArrayList<>();
            
            methods.add(onceMethod);
        
    
    

8.3 OnceMethod类

  • 需要讲的一点是,每一个使用了@OnceClick注解的Activity或View,都会为其生成一个代理类,而一个代理中有可能有很多个@OnceClick修饰的方法,所以我们专门为每个方法有创建了一个javaBean用于保存方法信息:
    public class OnceMethod 
    
        private int id;
        private String methodName;
        private List<String> methodParameters;
    
        OnceMethod(int id, String methodName, List<String> methodParameters) 
            this.id = id;
            this.methodName = methodName;
            this.methodParameters = methodParameters;
        
    
        int getMethodParametersSize() 
            return methodParameters == null ? 0 : methodParameters.size();
        
    
        int getId() 
            return id;
        
    
        String getMethodName() 
            return methodName;
        
    
        List<String> getMethodParameters() 
            return methodParameters;
        
    
    
    

关于其他内容介绍

01.关于博客汇总链接

02.关于我的博客

关于apt实践与总结开源库地址

https://github.com/yangchong211/YCApt

jquery点击事件案例

<!DOCTYPEhtml><htmllang="en"><head>  <metacharset="UTF-8">  <title>Document</title>  <scripttype="text/javascript"src="jquery.1.11. 查看详情

react框架实现点击事件计数小案例

代码块:importReactfrom‘react‘;importReactDOMfrom‘react-dom‘;import‘./index.css‘;//importAppfrom‘./App‘;importBrowserRouterasRouter,Route,Linkfrom‘react-router-dom‘importcreateStorefrom‘redux‘//import 查看详情

事件监听之统计登录用户人数(含用户名)案例

(一)创建一个User类,用于封装一个用户信息,该类实现了HttpSessionBindingListener接口中的valueBound()方法和valueUnbound()方法1packageentity;23importjavax.servlet.annotation.WebListener;4importjavax.servlet.http.*;56@WebListener()7publi 查看详情

学习react-简单小案例--点击事件

<html><head><title></title><metacharset="UTF-8"/><scriptsrc="js/react.min.js"type="text/javascript"charset="utf-8"></script><scriptsrc="js/react-dom.min.js 查看详情

vue点击事件之@click.native

参考技术Avue@click.native原生点击事件:1,给vue组件绑定事件时候,必须加上native,不然不会生效(监听根元素的原生事件,使用.native修饰符)2,等同于在子组件中:子组件内部处理click事件然后向外发送click事件:$emit("click&... 查看详情

关于js中的事件委托小案例

需求:页面上有一个按钮,和一个空的ul,要求点击按钮,会给ul中动态添加li元素,然后,点击动态添加的元素,在控制台上输出,这是第几个元素<ul></ul><button>点击增加</button>varul=document.querySelector(‘ul‘);varb... 查看详情

uniapp之uni-table组件添加行点击事件

uniapp之uni-table组件添加行点击事件第一种方案(不改变导入的tr组件)第二种方案(改变tr组件)写在末尾  由于uniapp官网的uni-table组件并没有给出添加行点击事件的events,于是自己想方法基于uni-table组件实... 查看详情

javascript中事件冒泡之实例理解

此#btnComfirmChooseCompany是Bootstrap模态弹层上的按钮,但点击后,点击事件被Bootstrap外层监听到了,效果就是模态弹出层被关闭了,所以,我不想这个点击事件被"传递"到"外层"页面.请注意参数e,一开始漏掉了,代码的效果没有满足我的预期$(... 查看详情

uniapp之uni-table组件添加行点击事件(代码片段)

uniapp之uni-table组件添加行点击事件第一种方案(不改变导入的tr组件)第二种方案(改变tr组件)写在末尾  由于uniapp官网的uni-table组件并没有给出添加行点击事件的events,于是自己想方法基于uni-table组件实... 查看详情

flowable事件之消息事件

...程实例,比如接收到了一封邮件,一条短信等,具体通过案例来讲解在这里插入图片描述我们需要先定义一个消息在这里插入图片描述然后在消息开始节点出引用在这里插入图片描述然后通过代码来处理,部署和启动部署后不会... 查看详情

androidstudio菜鸟实战项目之点击事件以及动态添加

原始界面:       登陆失败:      登陆成功:       动态添加控件: 布局如下:(特别声明最后又一个空linearlayout,这是为了后面的动态添加事件)<... 查看详情

enode框架conference案例分析系列之-事件溯源如何处理重构问题

前言本文可能对大多数不太了解ENode的朋友来说,理解起来比较费劲,这篇文章主要讲思路,而不是一上来就讲结果。我写文章,总是希望能把自己的思考过程尽量能表达出来,能让大家知道每一个设计背后的思考的东西。我觉... 查看详情

rxswift之订阅uitableviewcell里的按钮点击事件(代码片段)

...elected或modelSelected这两个Rx扩展方法,可以对单元格的点击事件进行响应,并执行相关的业务代码。但有时候并不需要整个cell都能进行点击响应,可能是点击单元格内的按钮时才触发相关的操作。二、效果展示点击单... 查看详情

多次点击事件

...(以及双击),虽然在设置中我们有发现多次点击事件的案例(如启用开发者模式),但Android本身并没有提供多次点击事件的实现。有如下两种实现方式:定义存贮多个事件点的变量首先以双击事件为例说明。定义一个存贮上... 查看详情

识别拖动与点击操作之zepto的bug

...题描述:给页面<a>标签绑定了tap事件,在移动设备上点击按钮貌似一切正常,可以响应。但是,把页面上下滑动几次之后,或者在滑动时手指滑动出移动屏幕之外,之后再点击按钮,就会发现第一次点击的时候事件没被触发... 查看详情

鸿蒙学习笔记之点击事件(代码片段)

...一篇中,分享了如何页面跳转。今天就继续详细讲讲点击事件,明天会继续分享双击事件和长按事件 1.点击事件在HarmonyOS中有四种点击事件,接下来我们分别介绍一下四种点击事件,以及如何实现1.自定义实现类... 查看详情

java之使用阿里云发短信项目案例以及源代码

一、开通阿里云短信功能官网:https://www.aliyun.com/product/sms1、登录注册2、创建账号点击右上角自定义对应的名称3、点击添加权限搜索短信点击完成记住这里的账号和密码4、添加签名等待审核通过5、添加模板等待审核通过二... 查看详情

java之使用阿里云发短信项目案例以及源代码

一、开通阿里云短信功能官网:https://www.aliyun.com/product/sms1、登录注册2、创建账号点击右上角自定义对应的名称3、点击添加权限搜索短信点击完成记住这里的账号和密码4、添加签名等待审核通过5、添加模板等待审核通过二... 查看详情