资讯专栏INFORMATION COLUMN

APT案例之点击事件

cyixlq / 812人阅读

摘要:杨充一定时间内该点击事件只能执行一次用来修饰这是一个什么类型的注解。杨充自定义编译器获取遍历,并生成代码配置文件文件配置的作用是向系统注册自定义注解处理器,执行编译时使用进行处理。

目录介绍

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/yangchong2...

如果觉得好,可以star一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!

关于apt实践与总结开源库地址 https://github.com/yangchong2... 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。就是我们自定义的注解。

/**
 * 
 *     @author 杨充
 *     blog  : https://github.com/yangchong211
 *     time  : 2017/06/21
 *     desc  : 一定time时间内该点击事件只能执行一次
 *     revise:

 */
//@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。

/**
 * 
 *     @author 杨充
 *     blog  : https://github.com/yangchong211
 *     time  : 2017/06/21
 *     desc  : 自定义Processor编译器
 *     revise:

 */
@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 annotations, RoundEnvironment roundEnv) {
        //获取proxyMap
        Map proxyMap = getProxyMap(roundEnv);
        //遍历proxyMap,并生成代码
        for (String key : proxyMap.keySet()) {
            OnceProxyInfo proxyInfo = proxyMap.get(key);
            writeCode(proxyInfo);
        }
        return true;
    }

    @Override
    public Set getSupportedAnnotationTypes() {
        Set 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 implements AbstractInjector {

    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 annotations, RoundEnvironment roundEnv) {
    //获取proxyMap
    Map 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 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("")
                .append(" implements AbstractInjector").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 getMethods() {
        return methods == null ? new ArrayList() : 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 methodParameters;

    OnceMethod(int id, String methodName, List 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 getMethodParameters() {
        return methodParameters;
    }

}

关于其他内容介绍 01.关于博客汇总链接

1.技术博客汇总

2.开源项目汇总

3.生活博客汇总

4.喜马拉雅音频汇总

5.其他汇总

02.关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

简书:http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo...

开源中国:https://my.oschina.net/zbj161...

泡在网上的日子:http://www.jcodecraeer.com/me...

邮箱:yangchong211@163.com

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault头条:https://segmentfault.com/u/xi...

掘金:https://juejin.im/user/593943...

关于apt实践与总结开源库地址 https://github.com/yangchong2...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73464.html

相关文章

  • 关于Apt注解实践与总结【包含20篇博客】

    摘要:使用实现功能运行期注解案例使用简单的注解,便可以设置布局,等效于使用实现路由综合型案例比较全面的介绍从零起步,一步一步封装简易的路由开源库。申明注解用的就是。返回值表示这个注解里可以存放什么类型值。 YCApt关于apt方案实践与总结 目录介绍 00.注解系列博客汇总 01.什么是apt 02.annotationProcessor和apt区别 03.项目目录结构 04.该案例作用 ...

    gnehc 评论0 收藏0
  • [系统安全] 三十五.Procmon工具基本用法及文件进程、注册表查看

    摘要:本文将分享软件基本用法及文件进程注册表查看,这是一款微软推荐的系统监视工具,功能非常强大可用来检测恶意软件。可以帮助使用者对系统中的任何文件注册表操作进行监视和记录,通过注册表和文件读写的变化,有效帮助诊断系统故障或发现恶意软件病毒及木马。 ...

    kk_miles 评论0 收藏0
  • 《网络黑白》一书所抄袭的文章列表

    摘要:网络黑白一书所抄袭的文章列表这本书实在是垃圾,一是因为它的互联网上的文章拼凑而成的,二是因为拼凑水平太差,连表述都一模一样,还抄得前言不搭后语,三是因为内容全都是大量的科普,不涉及技术也没有干货。 《网络黑白》一书所抄袭的文章列表 这本书实在是垃圾,一是因为它的互联网上的文章拼凑而成的,二是因为拼凑水平太差,连表述都一模一样,还抄得前言不搭后语,三是因为内容全都是大量的科普,不涉及技术...

    zlyBear 评论0 收藏0
  • URLOS应用开发入门案例——基于docker镜像制作一个可安装并可快速分发的gitbook应用

    摘要:是一个基于的命令行工具,可使用和来制作精美的电子书,并非关于的教程。使用制作电子书,必备两个文件和。今天,我就教大家如何使用快速制作应用,该应用是基于镜像制作,可以随意运行在任何一个安装有的主机上。首先安装安装完成后,地址栏输入即可访问。 GitBook 是一个基于 Node.js 的命令行工具,可使用 Github/Git 和 Markdown 来制作精美的电子书,GitBook 并...

    only_do 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<