博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
APT案例之点击事件
阅读量:5954 次
发布时间:2019-06-19

本文共 13239 字,大约阅读时间需要 44 分钟。

目录介绍

  • 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万字,陆续搬到网上],转载请注明出处,谢谢!
  • 链接地址:
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!

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

00.注解系列博客汇总

0.1 注解基础系列博客

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

    • 需要注意哪些要点?
    • 有哪些注入的方式可以解耦,你能想到多少?路由框架为何需要依赖注入?路由为何用注解进行依赖注入,而不是用反射方式注入,或者通过构造方法注入,或者通过接口方式注入?
    • 在跳转页面时,如何传递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文件夹,如图所示

    • image
  • 编译之后生成的代码

    // 编译生成的代码,不要修改// 更多内容:https://github.com/yangchong211package 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方法:真正生成代码的方法。
    • 总结一下:编译时,取得所有需要生成的代理类信息。遍历代理类集合,根据代理类信息,生成代码。
    @Overridepublic 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("// 编译生成的代码,不要修改\n"); builder.append("// 更多内容:https://github.com/yangchong211\n"); builder.append("package ").append(packageName).append(";\n\n"); //写入导包 builder.append("import android.view.View;\n"); builder.append("import com.ycbjie.api.Finder;\n"); builder.append("import com.ycbjie.api.AbstractInjector;\n"); builder.append('\n'); builder.append("public class ").append(proxyClassName) .append("
    ") .append(" implements AbstractInjector
    ").append(" {\n"); builder.append('\n'); generateInjectMethod(builder); builder.append('\n'); builder.append("}\n"); return builder.toString(); } private String getTargetClassName() { return targetClassName.replace("$", "."); } private void generateInjectMethod(StringBuilder builder) throws OnceClickException { builder.append(" public long intervalTime; \n"); builder.append('\n'); builder.append(" @Override \n") .append(" public void setIntervalTime(long time) {\n") .append(" intervalTime = time;\n } \n"); builder.append('\n'); builder.append(" @Override \n") .append(" public void inject(final Finder finder, final T target, Object source) {\n"); builder.append(" View view;"); builder.append('\n'); //这一步是遍历所有的方法 for (OnceMethod method : getMethods()) { builder.append(" view = ") .append("finder.findViewById(source, ") .append(method.getId()) .append(");\n"); builder.append(" if(view != null){\n") .append(" view.setOnClickListener(new View.OnClickListener() {\n") .append(" long time = 0L;\n"); builder.append(" @Override\n") .append(" public void onClick(View v) {\n"); builder.append(" long temp = System.currentTimeMillis();\n") .append(" if (temp - time >= intervalTime) {\n" + " time = temp;\n"); 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("\n }\n") .append(" }") .append("});\n }\n"); } builder.append(" }\n"); } 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:
  • 知乎:
  • 简书:
  • csdn:
  • 喜马拉雅听书:
  • 开源中国:
  • 泡在网上的日子:
  • 邮箱:yangchong211@163.com
  • 阿里云博客: 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:
  • 掘金:

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

你可能感兴趣的文章
【干货】界面控件DevExtreme视频教程大汇总!
查看>>
闭包 !if(){}.call()
查看>>
python MySQLdb安装和使用
查看>>
Java小细节
查看>>
poj - 1860 Currency Exchange
查看>>
chgrp命令
查看>>
Java集合框架GS Collections具体解释
查看>>
洛谷 P2486 BZOJ 2243 [SDOI2011]染色
查看>>
linux 笔记本的温度提示
查看>>
数值积分中的辛普森方法及其误差估计
查看>>
Web service (一) 原理和项目开发实战
查看>>
跑带宽度多少合适_跑步机选购跑带要多宽,你的身体早就告诉你了
查看>>
广平县北方计算机第一届PS设计大赛
查看>>
深入理解Java的接口和抽象类
查看>>
java与xml
查看>>
Javascript异步数据的同步处理方法
查看>>
iis6 zencart1.39 伪静态规则
查看>>
SQL Server代理(3/12):代理警报和操作员
查看>>
Linux备份ifcfg-eth0文件导致的网络故障问题
查看>>
2018年尾总结——稳中成长
查看>>