文章目录

  • 一、生成 Java 代码
  • 二、实现 IButterKnife 接口
  • 三、视图绑定主要操作
  • 四、完整注解处理器代码
  • 五、博客资源

Android APT 学习进阶路径 : 推荐按照顺序阅读 , 从零基础到开发简易 ButterKnife 注解框架的学习路径 ;

  • 【Java 注解】注解简介及作用
  • 【Java 注解】自定义注解 ( 注解属性定义与赋值 )
  • 【Java 注解】自定义注解 ( 元注解 )
  • 【Java 注解】自定义注解 ( 注解解析 )
  • 【Java 注解】自定义注解 ( 使用注解实现简单测试框架 )
  • 【Android APT】编译时技术 ( ButterKnife 原理分析 )
  • 【Android APT】编译时技术 ( 编译时注解 和 注解处理器 依赖库 )
  • 【Android APT】编译时技术 ( 开发编译时注解 )
  • 【Android APT】注解处理器 ( 注解标注 与 初始化方法 )
  • 【Android APT】注解处理器 ( 配置注解依赖、支持的注解类型、Java 版本支持 )
  • 【Android APT】注解处理器 ( Element 注解节点相关操作 )
  • 【Android APT】注解处理器 ( 生成代码并自动绑定控件 )

上一篇博客 【Android APT】注解处理器 ( Element 注解节点相关操作 )中 对 注解所标注的 节点 , 进行了获取及分析 , 将 VariableElement 类型的 注解节点 , 按照所在 Activity 进行了分组 ;

本篇博客开发 注解处理器 的 生成代码部分 ;

一、生成 Java 代码


上一篇博客 【Android APT】注解处理器 ( Element 注解节点相关操作 ) 中已经将 注解节点 , 按照 Activity 分组 , 放在了 HashMap<String, ArrayList<VariableElement>> elementMap 数据结构中 , 要生成的 .java 类的个数就是该 HashMap 键值对的个数 ;

目标是生成如下代码 :

package kim.hsl.apt;import android.view.View;public class MainActivity_ViewBinder implements IButterKnife<kim.hsl.apt.MainActivity> {public void bind(kim.hsl.apt.MainActivity target) {target.hello = target.findViewById(2131230899);}
}

逐个遍历 HashMap<String, ArrayList<VariableElement>> elementMap 数据结构 , 要从该 HashMap 中获取上述要生成代码的相关信息 ;

package kim.hsl.apt;

生成上述代码 , 需要获取包名 kim.hsl.apt , 根据 VariableElement 注解节点 , 获取 TypeElement 父节点 , 使用 ElementUtils 获取 TypeElement 节点对应的 PackageElement 包节点 , 调用该节点的 getQualifiedName 方法获取完整的包名信息 ;

//获取对应类的包名
// 获取 VariableElement 的父节点 TypeElement
TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();// 获取 Activity 名称
String activitySimpleName = typeElement.getSimpleName().toString();// 获取包节点
PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);// 获取包名
String packageName = packageElement.getQualifiedName().toString();
public class MainActivity_ViewBinder implements IButterKnife<kim.hsl.apt.MainActivity> {

生成上述代码 , 需要获取类名 , 以及完整的包名 和 类名 ; 调用 TypeElement 的 getSimpleName 方法 , 可以获取不带包名的类名 ;

// 获取类名
String className = activitySimpleName + "_ViewBinder";
    public void bind(kim.hsl.apt.MainActivity target) {target.hello = target.findViewById(2131230899);}
}

生成上述代码 , 其中 target.hello = target.findViewById(2131230899); 代码需要循环生成 , 该 Activity 中有多少变量添加了 @BindView 注解 , 就需要有几行上述代码 ;

// public void bind(kim.hsl.apt.MainActivity target){stringBuffer.append("public void bind(" + packageName + "." + activitySimpleName + " target){\n");for (VariableElement variableElement : variableElements){// 循环被注解的字段// 为每个 VariableElement 注解字段生成 target.findViewById(R.id.xxx); 代码// 获取成员变量名String variableName = variableElement.getSimpleName().toString();// 获取资源 id , 通过注解 , 获取注解属性 valueint resourceId = variableElement.getAnnotation(BindView.class).value();// target.stringBuffer.append("target." + variableName + " = target.findViewById(" + resourceId + ");\n");
}// }
stringBuffer.append("}\n");
// }
stringBuffer.append("}\n");

二、实现 IButterKnife 接口


该接口直接定义在主应用 , 上面的 注解处理器 本质上就是在 编译时 生成该接口的实现类 , 并实现了其中的 bind 方法 , 每个 Activity 界面都要 生成一个该接口的子类对象 , 在该 生成的 IButterKnife 子类中进行 组件的 findViewById 的视图绑定操作 ;

package kim.hsl.apt;public interface IButterKnife<T> {void bind(T target);
}

严谨一点的话 , 该接口一般是定义在 Android 依赖库 中 ;

三、视图绑定主要操作


在 Activity 界面中 , 调用

ButterKnife.bind(this);

方法 , 即可实现视图绑定操作 , 实际上是通过 Activity 的类名 “MainActivity” , 获取到生成的类名 “MainActivity_ViewBinder” , 通过反射获取该类对象 ;

直接创建该对象 , 并调用对象的 bind 方法 , 即可完成视图绑定 ;

ButterKnife 及静态 bind 方法实现 :

package kim.hsl.apt;public class ButterKnife {/*** 在 Activity 中调用该方法, 绑定接口* @param target*/public static void bind(Object target){String className = target.getClass().getName() + "_ViewBinder";try {// 通过反射得到 MainActivity_ViewBinder 类对象Class<?> clazz = Class.forName(className);// 调用生成的代码 MainActivity_ViewBinder 的 bind 方法if (IButterKnife.class.isAssignableFrom(clazz)){IButterKnife iButterKnife = (IButterKnife) clazz.newInstance();iButterKnife.bind(target);}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}
}

四、完整注解处理器代码


package kim.hsl.annotation_compiler;import com.google.auto.service.AutoService;import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;import kim.hsl.annotation.BindView;/*** 生成代码的注解处理器*/
@AutoService(Processor.class)
public class Compiler extends AbstractProcessor {/*** 生成 Java 代码对象*/private Filer mFiler;/*** 日志打印*/private Messager mMessager;/*** 初始化注解处理器相关工作* @param processingEnv*/@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);this.mFiler = processingEnv.getFiler();this.mMessager = processingEnv.getMessager();}/*** 声明 注解处理器 要处理的注解类型* @return*/@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> supportedAnnotationTypes = new HashSet<String>();// 将 BindView 全类名 kim.hsl.annotation.BinndView 放到 Set 集合中supportedAnnotationTypes.add(BindView.class.getCanonicalName());return supportedAnnotationTypes;}/*** 声明支持的 JDK 版本* @return*/@Overridepublic SourceVersion getSupportedSourceVersion() {// 通过 ProcessingEnvironment 类获取最新的 Java 版本并返回return processingEnv.getSourceVersion();}/*** 搜索 Android 代码中的 BindView 注解* 并生成相关代码* @param annotations* @param roundEnv* @return*/@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {// 搜索 BindView , 将 BindView 注解放在什么元素上 , 得到的就是相应类型的元素// 根据 注解类型 获取 被该注解类型 标注的元素 , 元素可能是类 , 方法 , 字段 ;// 通过 getElementsAnnotatedWith 方法可以搜索到整个 Module 中所有使用了 BindView 注解的元素Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);// @BindView 注解标注的都是 Activity 中的成员字段,// 上述 elements 中的元素都是 VariableElement 类型的节点HashMap<String, ArrayList<VariableElement>> elementMap = new HashMap<>();// 遍历 elements 注解节点 , 为节点分组for (Element element : elements){// 将注解节点类型强转为 VariableElement 类型VariableElement ve = (VariableElement) element;// 获取该注解节点对应的成员变量类名// 先获取该注解节点的上一级节点 , 注解节点是 VariableElement , 成员字段节点// 上一级节点是就是 Activity 类节点对应的 类节点 TypeElementTypeElement te = (TypeElement) ve.getEnclosingElement();// 获取 Activity 的全类名String activityFullName = te.getQualifiedName().toString();mMessager.printMessage(Diagnostic.Kind.NOTE, "TypeElement : " + activityFullName + " , VariableElement : " + ve.getSimpleName());// 获取 elementMap 集合中的 Activity 的全类名对应的 VariableElement 节点集合// 如果是第一次获取 , 为空 ,// 如果之前已经获取了该 Activity 的全类名对应的 VariableElement 节点集合, 那么不为空ArrayList<VariableElement> variableElements = elementMap.get(activityFullName);if (variableElements == null){variableElements = new ArrayList<>();// 创建之后 , 将集合插入到 elementMap 集合中elementMap.put(activityFullName, variableElements);}// 将本节点插入到 HashSet<VariableElement> variableElements 集合中variableElements.add(ve);}// 生成代码// 遍历 HashMap<String, HashSet<VariableElement>> elementMap 集合// 获取 Key 的迭代器Iterator<String> iterator = elementMap.keySet().iterator();while (iterator.hasNext()){// 获取 Activity 全类名String key = iterator.next();// 获取 Activity 下被注解标注的 VariableElement 注解节点ArrayList<VariableElement> variableElements = elementMap.get(key);//获取对应类的包名// 获取 VariableElement 的父节点 TypeElementTypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();// 获取 Activity 名称String activitySimpleName = typeElement.getSimpleName().toString();// 获取包节点PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);// 获取包名String packageName = packageElement.getQualifiedName().toString();// 获取类名String className = activitySimpleName + "_ViewBinder";// 写出文件的字符流Writer writer = null;// 获取到包名后 , 开始生成 Java 代码try {mMessager.printMessage(Diagnostic.Kind.NOTE, "Create Java Class Name : " + packageName + "." + className);// 根据 包名.类名_ViewBinder 创建 Java 文件JavaFileObject javaFileObject = mFiler.createSourceFile(packageName + "." + className);// 生成 Java 代码writer = javaFileObject.openWriter();// 生成字符串文本缓冲区StringBuffer stringBuffer = new StringBuffer();// 逐行写入文本到缓冲区中// package kim.hsl.apt;stringBuffer.append("package " + packageName +";\n");// import android.view.View;stringBuffer.append("import android.view.View;\n");// public class MainActivity_ViewBinding implements IButterKnife<kim.hsl.apt.MainActivity>{stringBuffer.append("public class " + className + " implements IButterKnife<" + packageName + "." + activitySimpleName +">{\n");//stringBuffer.append("public class " + className +"{\n");// public void bind(kim.hsl.apt.MainActivity target){stringBuffer.append("public void bind(" + packageName + "." + activitySimpleName + " target){\n");for (VariableElement variableElement : variableElements){// 循环被注解的字段// 为每个 VariableElement 注解字段生成 target.findViewById(R.id.xxx); 代码// 获取成员变量名String variableName = variableElement.getSimpleName().toString();// 获取资源 id , 通过注解 , 获取注解属性 valueint resourceId = variableElement.getAnnotation(BindView.class).value();// target.stringBuffer.append("target." + variableName + " = target.findViewById(" + resourceId + ");\n");}// }stringBuffer.append("}\n");// }stringBuffer.append("}\n");mMessager.printMessage(Diagnostic.Kind.NOTE, "stringBuffer.toString() : " + stringBuffer.toString());mMessager.printMessage(Diagnostic.Kind.NOTE, "writer : " + writer);// 将字符串缓冲区的数据写出到 Java 文件中writer.write(stringBuffer.toString());mMessager.printMessage(Diagnostic.Kind.NOTE,"write finished");} catch (Exception e) {mMessager.printMessage(Diagnostic.Kind.NOTE,"IOException");e.printStackTrace();}finally {if (writer != null){try {mMessager.printMessage(Diagnostic.Kind.NOTE,"write closed");writer.close();} catch (IOException e) {e.printStackTrace();}}}}mMessager.printMessage(Diagnostic.Kind.NOTE,"process finished");return false;}
}

五、博客资源


博客源码 :

  • GitHub : https://github.com/han1202012/APT

  • CSDN : https://download.csdn.net/download/han1202012/18917831

【Android APT】注解处理器 ( 根据注解生成 Java 代码 )相关推荐

  1. 【Android APT】注解处理器 ( Element 注解节点相关操作 )

    文章目录 一.获取被 注解 标注的节点 二.Element 注解节点类型 三.VariableElement 注解节点相关操作 四.注解处理器 完整代码示例 五.博客资源 Android APT 学习 ...

  2. java静态注解处理器_深入理解Java:注解(Annotation)--注解处理器

    如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了.使用注解的过程中,很重要的一部分就是创建于使用注解处理器.Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处 ...

  3. javapoet动态生成java代码

    刚接触第一感觉 动态生成java代码???java代码不就是一个后缀名为.java的txt文档吗?仔细想了下,如果要生成这样的代码的确挺麻烦,你要考虑导包的问题,以及复杂的语法 javapoet代码仓 ...

  4. java插件开发_编写一个IDEA插件之:自动生成Java代码

    我很喜欢IDEA的一键自动生成代码功能,例如自动生成构造方法.字段的Get/Set方法.ToString方法等等,除此之外,也有一些插件提供自动生成代码的功能,例如我们所熟悉的GsonFormat插件 ...

  5. CXF wsdl2java 生成java代码供客户端使用

    CXF wsdl2java 生成java代码供客户端使用 环境配置: 1.下载apache-cxf-2.6.2在环境变量中配置CXF_HOME 值为E:\gavin\cxf\apache-cxf-3. ...

  6. Protobuf生成Java代码(命令行)

    1.说明 本文介绍Protobuf生成Java代码的方法, 下载必须的Protobuf工具, 然后通过命令行, 把.proto文件生成Java代码. 2.准备Protobuf工具 2.1.获取prot ...

  7. Protobuf生成Java代码(Maven)

    1.说明 本文介绍Protobuf生成Java代码的方法, 配置对应的Maven插件, 把.proto文件生成Java代码. 2.插件配置 创建Maven工程grpc-compile, 修改pom.x ...

  8. 通过物理模型生成Java代码

    通过物理模型生成Java代码 软件开发过程中,我们一般是先针对数据库建模,物理建模完成后,生成数据库表,编码阶段的时候我们会针对数据库表生成大量的Javaeban或者是实体类 Powertdesign ...

  9. 分享sina的短链生成java代码

    比如可以将http://zuidaima.com/转换为http://t.cn/zlsvWVq 如下图: 原创不易,转载请注明出处分享sina的短链生成java代码 package com.zuida ...

最新文章

  1. 开发者和矿工合二为一将是比特币世界的灾难
  2. 线性表的链式表示——单链表
  3. 网络推广产品浅析网站想要保持稳定的SEO排名和流量需要做什么?
  4. javascript中函数作用域和声明提前
  5. android 勿扰模式代码,android Lollipop勿扰模式
  6. 错误代码#1045 Access denied for user 'root'@'localhost' (using password:YES)
  7. WindowsXP命令行修改服务启动选项
  8. html 闪烁字,HTML最简单的文字闪烁代码
  9. 前端之 JavaScript 基础
  10. 【炼丹技巧】指数移动平均(EMA)【在一定程度上提高最终模型在测试数据上的表现(例如accuracy、FID、泛化能力...)】
  11. Spring Security 示例教程
  12. Excel从右向左查找
  13. anaconda安装numpy_Python3.8如何安装Numpy
  14. 【安装包】eclipse
  15. linux为mysql创建gpower_实战:在Java Web 项目中使用HBase
  16. Mac上将mp4视频做成屏保
  17. 安卓5.1自带浏览器主页设置–转载
  18. 双向链表增删改查分析
  19. [机缘参悟-58]:《素书》-5-奉行仁义[遵义章第五]
  20. python 三七小说爬虫小记

热门文章

  1. Python Xml类
  2. [Ubuntu] SVN常用的批量操作
  3. js在firefox中的问题
  4. Zepto.js简介
  5. Ubuntu里解压tar.xz格式
  6. SAS 对数据的拼接与串接
  7. sqlserver 查找数据混排
  8. 【C/C++开发】C++实现简单的线程池
  9. 预备作业01:你期望的师生关系是什么?
  10. 多个流,简短的读和写