Android APT注解处理器 ( 根据注解生成 Java 代码 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android APT注解处理器 ( 根据注解生成 Java 代码 )相关的知识,希望对你有一定的参考价值。
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 , 通过注解 , 获取注解属性 value
int 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
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.mFiler = processingEnv.getFiler();
this.mMessager = processingEnv.getMessager();
}
/**
* 声明 注解处理器 要处理的注解类型
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new HashSet<String>();
// 将 BindView 全类名 kim.hsl.annotation.BinndView 放到 Set 集合中
supportedAnnotationTypes.add(BindView.class.getCanonicalName());
return supportedAnnotationTypes;
}
/**
* 声明支持的 JDK 版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
// 通过 ProcessingEnvironment 类获取最新的 Java 版本并返回
return processingEnv.getSourceVersion();
}
/**
* 搜索 Android 代码中的 BindView 注解
* 并生成相关代码
* @param annotations
* @param roundEnv
* @return
*/
@Override
public 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 类节点对应的 类节点 TypeElement
TypeElement 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 的父节点 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();
// 获取类名
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 , 通过注解 , 获取注解属性 value
int 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;
}
}
五、博客资源
博客源码 :
以上是关于Android APT注解处理器 ( 根据注解生成 Java 代码 )的主要内容,如果未能解决你的问题,请参考以下文章
Android APT注解处理器 ( 注解标注 与 初始化方法 )
Android apt被注解为Kotlin写的代码时不能自动生成文件bug
java 编程基础:注解(Annotation Processing Tool)注解处理器 利用注解解读类属性生成XML文件