1. 创建注解
1.1 声明在什么类型上
@Target(ElementType.FIELD)
ElementType类型
public enum ElementType {
// 类、接口(包括注解类型)或枚举声明。
TYPE,
// 成员变量声明(包括枚举常量)。
FIELD,
// 方法声明。
METHOD,
// 参数声明。
PARAMETER,
// 构造方法声明。
CONSTRUCTOR,
// 局部变量声明。
LOCAL_VARIABLE,
// 注解类型声明。
ANNOTATION_TYPE,
// 包声明。
PACKAGE,
// 泛型声明
TYPE_PARAMETER,
// 没有使用限制的声明 以上都包含
TYPE_USE;
}
1.2 声明注解的生命周期
@Retention(RetentionPolicy.CLASS)
** RetentionPolicy 类型 **
public enum RetentionPolicy {
// 源码期
SOURCE,
// 编译期
CLASS,
// 运行期
RUNTIME;
private RetentionPolicy() {
}
}
2. 创建注解处理器(必须是 Java/Kotlin 的 jar 项目)
目的 在运行时生成对应的类,处理对应的逻辑的代码 注意事项 注解处理器,不能使用 log 打印日志,也不能进行 debug 运行
2.1 引入第三方库
dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.google.auto.service:auto-service:1.0-rc7'
}
2.2 创建注解处理器类,并声明 Java 支持的版本和需要处理的注解有哪些(有以下两种方法)
2.2.1 使用代码声明
/**
* 注解处理器
*
* @// TODO: 2024/1/4 创建对应的类,比如viewHolder 处理对应的逻辑
*/
@AutoService(Processor.class) // 注册这个类是注解处理器
public class AnnotationCompiler extends AbstractProcessor {
/**
* 声明注解处理器支持的 Java 版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
// Java 当前版本
return processingEnv.getSourceVersion();
}
/**
* 声明注解处理器要处理的注解有哪些
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
// 返回的是 BindView 注解的规范化类名(Canonical Name)。规范化类名是一个完全限定的类名,包括包路径,但不包括内部类或简化的类名。
types.add(BindView.class.getCanonicalName());
// 返回的是 OnClick 注解的规范化类名(Canonical Name)。规范化类名是一个完全限定的类名,包括包路径,但不包括内部类或简化的类名。
types.add(OnClick.class.getCanonicalName());
return types;
}
/**
* 打印日志
*/
public void logUtil(String log) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, log);
}
}
2.2.2 使用注解声明
/**
* 注解处理器
*
* @// TODO: 2024/1/4 创建对应的类,比如viewHolder 处理对应的逻辑
*/
@AutoService(Processor.class) // 注册这个类是注解处理器
@SupportedAnnotationTypes({"包名+类名", "包名+类名"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationCompiler extends AbstractProcessor {
/**
* 打印日志
*/
public void logUtil(String log) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, log);
}
}
2.3 创建一个容器,用来保存成员变量和方法注解
public class ElementForType {
// 成员变量的节点
private List<VariableElement> viewElement;
// 方法的节点
private List<ExecutableElement> methodElement;
public List<VariableElement> getViewElement() {
return viewElement;
}
public void setViewElement(List<VariableElement> viewElement) {
this.viewElement = viewElement;
}
public List<ExecutableElement> getMethodElement() {
return methodElement;
}
public void setMethodElement(List<ExecutableElement> methodElement) {
this.methodElement = methodElement;
}
}
2.4. BindView 注解
2.4.1. 获取 BindView 绑定的所有的控件
Set<? extends Element> viewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
2.4.2. 循环得到每个节点的控件名
for (Element viewElement : viewElements) {
// 遍历所有的成员变量节点 获取每一个成员变量的节点
VariableElement variableElement = (VariableElement) viewElement;
}
2.4.3. 得到获取节点的父节点,因为是绑定的控件,也就是成员变量,所以父节点就是 Activity
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
2.4.4. 得到父节点下的容器中所有的注解
ElementForType elementForType = map.get(typeElement);
2.4.5. 从容器中拿到成员变量控件的集合,如果存在,直接获取,如果不存在,新建一个集合,并放入所有节点的 map 中
// key: activity value: 注解容器
Map<TypeElement, ElementForType> map = new HashMap<>();
List<VariableElement> variableElements;
if (elementForType != null) {
// 获取 MainActivity 下边的所有的通过 BindView 绑定的成员变量的节点
variableElements = elementForType.getViewElement();
// 如果不存在 BindView 注解
if (variableElements == null) {
variableElements = new ArrayList<>();
elementForType.setViewElement(variableElements);
}
} else {
// 如果为空 就创建
elementForType = new ElementForType();
variableElements = new ArrayList<>();
elementForType.setViewElement(variableElements);
if (!map.containsKey(typeElement)) {
map.put(typeElement, elementForType);
}
}
2.4.6. 将获取的节点放入到容器的结合中,和 Activity 形成一一对应的关系
variableElements.add(variableElement);
2.5 OnClick 同样的处理
// 获取应用中 OnClick 标记的方法节点
Set<? extends Element> methodElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
for (Element methodElement : methodElements) {
ExecutableElement executableElement = (ExecutableElement) methodElement;
TypeElement typeElement = (TypeElement) methodElement.getEnclosingElement();
// 获取容器
ElementForType elementForType = map.get(typeElement);
List<ExecutableElement> executableElements;
if (elementForType != null) {
executableElements = elementForType.getMethodElement();
// 如果不存在 OnClick 注解
if (executableElements == null) {
executableElements = new ArrayList<>();
elementForType.setMethodElement(executableElements);
}
} else {
elementForType = new ElementForType();
executableElements = new ArrayList<>();
elementForType.setMethodElement(executableElements);
if (!map.containsKey(typeElement)) {
map.put(typeElement, elementForType);
}
}
executableElements.add(executableElement);
}
2.6. 写文件
2.6.1. 初始化
/*用来创建文件的*/
private Filer filer;
/**
* 初始化的方法
*
* @param processingEnv 工具类
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
2.6.2. 创建 Activity_ViewHolder,实际上就是按照类的格式一样手写一个类,包含包名,导包,类名,构造等
if (!map.isEmpty()) {
Iterator<TypeElement> iterator = map.keySet().iterator();
Writer writer = null;
while (iterator.hasNext()) {
// 得到类节点
TypeElement typeElement = iterator.next();
// 得到容器
ElementForType elementForType = map.get(typeElement);
// 获取类名
String className = typeElement.getSimpleName().toString();
// 获取包名
String packageName = getPackageName(typeElement);
// 创建一个新的类名
String newClazzName = className + "_ViewHolder";
// 创建文件
try {
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + newClazzName);
// 写内容
writer = sourceFile.openWriter();
StringBuffer sb = getStringBuffer(packageName, newClazzName, typeElement, elementForType);
writer.write(sb.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
if(writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
private StringBuffer getStringBuffer(String packageName, String newClazzName, TypeElement typeElement, ElementForType elementForType) {
StringBuffer sb = new StringBuffer();
sb.append("package " + packageName + ";\n");
sb.append("import android.view.View;\n");
sb.append("public class " + newClazzName + "{\n");
// 构造 参数(包名+类名 target)
sb.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");
/**
* @// TODO: BindView 注解
*/
// @BindView(R.id.text)
// TextView textView;
if (elementForType != null && elementForType.getViewElement() != null && !elementForType.getViewElement().isEmpty()) {
List<VariableElement> viewElements = elementForType.getViewElement();
for (VariableElement viewElement : viewElements) {
// 获取类型,是 TextView 还是 Button
TypeMirror typeMirror = viewElement.asType();
// 获取控件名
Name simpleName = viewElement.getSimpleName();
// 拿到资源ID (BindView 注解括号中的 ID, 就是 text)
int resId = viewElement.getAnnotation(BindView.class).value();
// MainActivity.textView = (TextView)MainActivity.findViewById(R.id.text);
sb.append("target." + simpleName + " = (" + typeMirror + ")target.findViewById(" + resId + ");\n");
}
}
/**
* @// TODO: OnClick 注解
*/
// textView.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// executableElement(view);
// }
// });
if(elementForType != null && elementForType.getMethodElement() != null && !elementForType.getMethodElement().isEmpty()) {
List<ExecutableElement> methodElement = elementForType.getMethodElement();
for (ExecutableElement executableElement : methodElement) {
int[] resIds = executableElement.getAnnotation(OnClick.class).value();
// 获取到 OnClick 注解的方法名
String methodName = executableElement.getSimpleName().toString();
for (int resId : resIds) {
sb.append("target.findViewById(" + resId + ").setOnClickListener(new View.OnClickListener() {\n");
sb.append("public void onClick(View view) {\n");
sb.append("target." + methodName + "(view);\n");
sb.append("}\n});\n");
}
}
}
sb.append("}\n}\n");
return sb;
}
2.7. 生成的 ViewHolder 格式如下
package com.dome.butter;
import android.view.View;
public class MainActivity_ViewHolder {
public MainActivity_ViewHolder(final com.dome.butter.MainActivity target) {
target.textView = (android.widget.TextView) target.findViewById(2131230980);
target.findViewById(2131230980).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
target.onClick(view);
}
});
}
}
2.8. 如何使用
public class ButterKnife {
public static void bind(Object clazz) {
String name = clazz.getClass().getName();
String bindName = name + "_ViewHolder";
try {
// 获取类
Class<?> aClass = Class.forName(bindName);
// 获取构造
Constructor<?> constructor = aClass.getConstructor(clazz.getClass());
// 使用构造
constructor.newInstance(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.9. 至此,一个基本的 ButterKnife 完成,下边贴出构建起完成代码
// 注册这个类是注解处理器
@AutoService(Processor.class)
// 声明需要处理的注解有哪些
@SupportedAnnotationTypes({"com.dome.annotation.BindView", "com.dome.annotation.OnClick"})
// 声明需要支持的 Java 版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationCompiler extends AbstractProcessor {
/*用来创建文件的*/
private Filer filer;
/**
* 初始化的方法
*
* @param processingEnv 工具类
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
/**
* 注解处理器的核心方法
* <p/> 所有要做的业务逻辑处理 都在这个方法中
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 将所有的节点 都以类的方式进行一一对应
Map<TypeElement, ElementForType> map = findAndParserTarget(roundEnvironment);
if (!map.isEmpty()) {
Iterator<TypeElement> iterator = map.keySet().iterator();
Writer writer = null;
while (iterator.hasNext()) {
// 得到类节点
TypeElement typeElement = iterator.next();
// 得到容器
ElementForType elementForType = map.get(typeElement);
/**
* @// TODO: 开始处理文件
*/
// 获取类名
// 全路径,存在包名
// typeElement.getQualifiedName();
// 只有名字,没有包名
String className = typeElement.getSimpleName().toString();
// 获取包名
String packageName = getPackageName(typeElement);
// 创建一个新的类名
String newClazzName = className + "_ViewHolder";
// 创建文件
try {
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + newClazzName);
// 写内容
writer = sourceFile.openWriter();
StringBuffer sb = getStringBuffer(packageName, newClazzName, typeElement, elementForType);
writer.write(sb.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
if(writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
/**
* 类和注解一一对应
*
* @param roundEnvironment
* @return
* @// TODO: 2024/1/4 Map<TypeElement, ElementForType>
* TypeElement: 类 ElementForType: 当前类中所有的注解,目前声明了成员变量和方法
*/
private Map<TypeElement, ElementForType> findAndParserTarget(RoundEnvironment roundEnvironment) {
// key: activity value: 注解容器
Map<TypeElement, ElementForType> map = new HashMap<>();
// BindView 注解处理
getViewAnnotation(map, roundEnvironment);
// OnClick 注解处理
getMethodAnnotation(map, roundEnvironment);
return map;
}
/**
* BindView 注解处理
*
* @// TODO: 以下代码以 MainActivity 说明
*/
public void getViewAnnotation(Map<TypeElement, ElementForType> map, RoundEnvironment roundEnvironment) {
// 获取应用中 BindView 标记的节点 包含 MainActivity 和 TwoActivity 通过 BindView 的控件
Set<? extends Element> viewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element viewElement : viewElements) {
/**
* @// TODO: 获取到绑定的节点的注解
*/
// 遍历所有的成员变量节点 获取每一个成员变量的节点
VariableElement variableElement = (VariableElement) viewElement;
/**
* @// TODO: 得到父节点
*/
// 获取到成员变量的类节点 getEnclosingElement()可以获取当前节点的上一个节点
// variableElement(成员变量)的节点的上一个节点就是类节点
// 找到 MainActivity 的节点
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
/**
* @// TODO: 得到父节点注解的容器 ElementForType(ElementForType 包含成员变量和方法注解)
*/
// 获取 MainActivity 节点中的值
ElementForType elementForType = map.get(typeElement);
/**
* @// TODO: 获取对应的集合
*/
List<VariableElement> variableElements;
if (elementForType != null) {
// 获取 MainActivity 下边的所有的通过 BindView 绑定的成员变量的节点
variableElements = elementForType.getViewElement();
// 如果不存在 BindView 注解
if (variableElements == null) {
variableElements = new ArrayList<>();
elementForType.setViewElement(variableElements);
}
} else {
// 如果为空 就创建
elementForType = new ElementForType();
variableElements = new ArrayList<>();
elementForType.setViewElement(variableElements);
if (!map.containsKey(typeElement)) {
map.put(typeElement, elementForType);
}
}
/**
* @// TODO: 给集合添加数据
*/
// 将拿到的控件注解放入到对应的集合中
variableElements.add(variableElement);
}
}
/**
* OnClick 注解处理
*/
public void getMethodAnnotation(Map<TypeElement, ElementForType> map, RoundEnvironment roundEnvironment) {
// 获取应用中 OnClick 标记的方法节点
Set<? extends Element> methodElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
for (Element methodElement : methodElements) {
ExecutableElement executableElement = (ExecutableElement) methodElement;
TypeElement typeElement = (TypeElement) methodElement.getEnclosingElement();
// 获取容器
ElementForType elementForType = map.get(typeElement);
List<ExecutableElement> executableElements;
if (elementForType != null) {
executableElements = elementForType.getMethodElement();
// 如果不存在 OnClick 注解
if (executableElements == null) {
executableElements = new ArrayList<>();
elementForType.setMethodElement(executableElements);
}
} else {
elementForType = new ElementForType();
executableElements = new ArrayList<>();
elementForType.setMethodElement(executableElements);
if (!map.containsKey(typeElement)) {
map.put(typeElement, elementForType);
}
}
executableElements.add(executableElement);
}
}
/**
* 写文件 拼装生成文件的代码
*
* @param packageName
* @param newClazzName
* @param typeElement
* @param elementForType
* @return
*/
private StringBuffer getStringBuffer(String packageName, String newClazzName, TypeElement typeElement, ElementForType elementForType) {
StringBuffer sb = new StringBuffer();
sb.append("package " + packageName + ";\n");
sb.append("import android.view.View;\n");
sb.append("public class " + newClazzName + "{\n");
// 构造 参数(包名+类名 target)
sb.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");
/**
* @// TODO: BindView 注解
*/
// @BindView(R.id.text)
// TextView textView;
if (elementForType != null && elementForType.getViewElement() != null && !elementForType.getViewElement().isEmpty()) {
List<VariableElement> viewElements = elementForType.getViewElement();
for (VariableElement viewElement : viewElements) {
// 获取类型,是 TextView 还是 Button
TypeMirror typeMirror = viewElement.asType();
// 获取控件名
Name simpleName = viewElement.getSimpleName();
// 拿到资源ID (BindView 注解括号中的 ID, 就是 text)
int resId = viewElement.getAnnotation(BindView.class).value();
// MainActivity.textView = (TextView)MainActivity.findViewById(R.id.text);
sb.append("target." + simpleName + " = (" + typeMirror + ")target.findViewById(" + resId + ");\n");
}
}
/**
* @// TODO: OnClick 注解
*/
// textView.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// executableElement(view);
// }
// });
if(elementForType != null && elementForType.getMethodElement() != null && !elementForType.getMethodElement().isEmpty()) {
List<ExecutableElement> methodElement = elementForType.getMethodElement();
for (ExecutableElement executableElement : methodElement) {
int[] resIds = executableElement.getAnnotation(OnClick.class).value();
// 获取到 OnClick 注解的方法名
String methodName = executableElement.getSimpleName().toString();
for (int resId : resIds) {
sb.append("target.findViewById(" + resId + ").setOnClickListener(new View.OnClickListener() {\n");
sb.append("public void onClick(View view) {\n");
sb.append("target." + methodName + "(view);\n");
sb.append("}\n});\n");
}
}
}
sb.append("}\n}\n");
return sb;
}
/**
* 获取包名
*
* @param typeElement
* @return
*/
private String getPackageName(Element typeElement) {
PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(typeElement);
Name name = packageOf.getQualifiedName();
return name.toString();
}
/**
* 打印日志
*/
public void logUtil(String log) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, log);
}
}
3. Element 类型
TypeElement(类型元素):表示一个类或接口元素。TypeElement代表类、接口、枚举或注解类型。 VariableElement(变量元素):表示一个字段、enum常量、方法或构造方法的参数、局部变量或异常参数。 (ExecutableElement(可执行元素):表示一个方法、构造方法或初始化程序(静态或实例)。 PackageElement(包元素): 表示一个包程序元素。 Parameterizable(可参数化的元素):代表可以具有类型参数的元素,例如泛型类、接口、方法或构造方法。 TypeParameterElement(类型参数元素): 表示一个类型参数。 UnknownElement:未知元素类型。在某些情况下,可能会遇到无法识别的元素。