【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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
逐个遍历 HashMap<String, ArrayList<VariableElement>> elementMap
数据结构 , 要从该 HashMap
中获取上述要生成代码的相关信息 ;
package kim.hsl.apt;
- 1
生成上述代码 , 需要获取包名 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();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
public class MainActivity_ViewBinder implements IButterKnife<kim.hsl.apt.MainActivity> {
- 1
生成上述代码 , 需要获取类名 , 以及完整的包名 和 类名 ; 调用 TypeElement 的 getSimpleName 方法 , 可以获取不带包名的类名 ;
// 获取类名
String className = activitySimpleName + "_ViewBinder";
- 1
- 2
public void bind(kim.hsl.apt.MainActivity target) {
target.hello = target.findViewById(2131230899);
}
}
- 1
- 2
- 3
- 4
生成上述代码 , 其中 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");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
二、实现 IButterKnife 接口
该接口直接定义在主应用 , 上面的 注解处理器 本质上就是在 编译时 生成该接口的实现类 , 并实现了其中的 bind
方法 , 每个 Activity
界面都要 生成一个该接口的子类对象 , 在该 生成的 IButterKnife
子类中进行 组件的 findViewById
的视图绑定操作 ;
package kim.hsl.apt;
public interface IButterKnife<T> {
void bind(T target);
}
- 1
- 2
- 3
- 4
- 5
严谨一点的话 , 该接口一般是定义在 Android 依赖库 中 ;
三、视图绑定主要操作
在 Activity 界面中 , 调用
ButterKnife.bind(this);
- 1
方法 , 即可实现视图绑定操作 , 实际上是通过 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();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
四、完整注解处理器代码
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
五、博客资源
博客源码 :
-
GitHub : https://github.com/han1202012/APT
-
CSDN : https://download.csdn.net/download/han1202012/18917831
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/117076846
- 点赞
- 收藏
- 关注作者
评论(0)