通过以上的分析我们就可以明确以下 极简 步骤:
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
顺手Sync下
implementation project(path: ':annotations')
annotationProcessor project(path: ':annotation_compiler')
annotation_compiler module 依赖
implementation project(path: ':annotations')
准备工作到此结束
先看结构再看码
在 annotation module 中新建两个注解类
分别实现以下方法:
绑定控件的注解是一个注解对应一个控件,所以每次只获取一个控件Id,注解接收一个int值
绑定点击事件的注解需要接收多个控件的Id,所以注解接收一个int[]
备注:
每一个带有 @BindView 、@OnClick 注解声明的Activity类都要相应的生成一个XXActivity_ViewBinding类,并在类中声明一个 bind(Activity target) 方法,该方法能够接收 不同的Activity对象;
注解处理类的功能才是真正用来进行注解处理并且创建我们需要的java类文件的
a. 在module(annotation_compiler)下新建AnnotationComPiler类
@AutoService(Processor.class) //注册注解处理器,此处用到环境配置中依赖的AutoService库
public class AnnotationCompiler extends AbstractProcessor {}
b. 声明Filer对象
注意:Filer对象配合Writer对象可以创建一个有内容的文件
//声明文件对象(成员变量)
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//就是这么写的,别问
filer = processingEnvironment.getFiler();
}
c. 重写getSupportedAnnotationTypes(),筛选我们的目标注解
/**
* 声明注解处理器要处理的注解
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
//getCanonicalName()获取的是包名+类名
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
d. 重写getSupportedSourceVersion(),声明支持的java版本
这里我们用默认就可以
/**
* 声明注解处理器支持的java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
//processingEnv这是父类变量
return processingEnv.getSourceVersion();
}
d. 重写process(),生成XXActivity_ViewBinding类文件
首先看一下我们需要的XXActivity_ViewBinding类的样子
//声明包路径
package com.junt.annotationdemo;
//由于实现了Binder接口,所以要声明导入Binder
import com.junt.annotationdemo.Binder;
//点击事件需要实例化View.OnClickListener()接口,所以还要声明导入View包
import android.view.View;
public class MainActivity_ViewBinding implements Binder<com.junt.annotationdemo.MainActivity>{
@Override
public void bind(final com.junt.annotationdemo.MainActivity target) {
//首先对于即用到了@BindView又用到了@OnClick的控件,我们在生成代码时应该需要以下内容
//textView-说明我们需要获取控件名,
//(android.widget.TextView)-说明我们还需要知道控件的类型
//2131165319-控件的Id
target.textView=(android.widget.TextView)target.findViewById(2131165319);
target.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
target.onClick(view);
} catch (Exception e) {
e.printStackTrace();
}
}
});
//其次,对于仅使用了@OnClick的控件,仅需要一个Id即可
target.findViewById(2131165320).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
target.onClick(view);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
在生成的java类文件中写入代码说明
比如我们需要写入
package com.junt.annotationdemo;
只需要调用
writer.write("package com.junt.annotationdemo;\n")
全部流程代码
/**
* 写一个自动findViewById的文件
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取所有用到@BindView的节点元素
Set extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//根据节点所在的Activity进行分类(一个Activity中会有多个@BindBiew),便于接下来创建每一个单独的XXActivity_ViewBinding类文件
Map> mapBindView = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
//获取控件元素
VariableElement variableElement = (VariableElement) element;
//获取控件元素的父元素Name(activity或fragment)
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
List variableElements = mapBindView.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList<>();
mapBindView.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
//获取所有用到@onClick的节点
Set extends Element> elementsAnnotatedWithClick=roundEnvironment.getElementsAnnotatedWith(OnClick.class);
//同样根据Activity进行分类(一个Activity中仅有一个@OnClick)
Map mapClickView = new HashMap<>();
for (Element element : elementsAnnotatedWithClick) {
String activityName = element.getEnclosingElement().getSimpleName().toString();
mapClickView.put(activityName, element);
}
//开始创建XXActivity_ViewBinding类文件
Writer writer = null;
//用For循环,每一次循环写一个xxActivity_ViewBinding
for (String activityName : mapBindView.keySet()) {
//取出activityName下的带注解控件元素
List variableElements = mapBindView.get(activityName);
//获取activity所在包名(同一个Activity下的元素的父元素都是该Activity,所以任意取一个就行,这里取0)
Element enclosingElement = variableElements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
try {
//实例化一个JavaFileObject对象(我们写的是.java文件--类文件)
JavaFileObject sourceFile = filer.createSourceFile(
packageName + "." + activityName + "_ViewBinding");
//实例化writer对象用来写入向.java文件中写入代码
writer = sourceFile.openWriter();
//1.写入-导包代码
writer.write("package " + packageName + ";\n");
writer.write("import " + packageName + ".Binder;\n");
writer.write("import android.view.View;\n");
//2.写入-声明类及实现接口代码
writer.write("public class " + activityName + "_ViewBinding implements Binder<"
+ packageName + "." + activityName + ">{\n");
//3.写入-实现接口方法代码
writer.write(" @Override\n" +
" public void bind(final " + packageName + "." + activityName + " target) {\n");
//取出带onCLick注解的元素Id
Element clickVariableElement = mapClickView.get(activityName);
int[] clickIds = clickVariableElement.getAnnotation(OnClick.class).value();
List id = new ArrayList<>(clickIds.length);
//4.写入-所有控件findViewById代码
for (VariableElement variableElement : variableElements) {
//获取控件Name
String variableName = variableElement.getSimpleName().toString();
//获取控件ID
int variableId = variableElement.getAnnotation(BindView.class).value();
//获取控件Type
TypeMirror typeMirror = variableElement.asType();
//写FindViewById
writeFindViewById(writer, variableName, typeMirror, variableId);
//同时看看这个控件元素有没有@OnClick注解,有的话同时写setOnClickListener
if (contains(clickIds, variableId)) {
//已经设置过点击事件的所有控件Id
id.add(variableId);
writeSetOnClickListener(writer, packageName, activityName, variableName);
}
}
for (int clickId : clickIds) {
//如果id集合中没有这个clickId则说明这个空间需要设置点击事件,但还没设置点击事件
if (!id.contains(clickId)) {
writeSetOnClickListenerWithoutName(writer, packageName, activityName, clickId);
}
}
//5.写入-补全类的构造
writer.write(" \n }\n}\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
//这个Activity已经设置过Id绑定与点击事件、故移除
mapClickView.remove(activityName);
if (writer != null) {
try {
writer.close();
writer = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//处理Activity中仅有@OnClick绑定的情况,上面的步骤仅是处理了所有含有@BindView的Activity(点击事件也同时处理并且从mapClickView中移除)
if (mapClickView.size() <= 0) {
return false;
}
for (String activityName : mapClickView.keySet()) {
Element element = mapClickView.get(activityName);
String packageName = processingEnv.getElementUtils().getPackageOf(element.getEnclosingElement()).toString();
try {
JavaFileObject sourceFile = filer.createSourceFile(
packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
//1.写入-导包代码
writer.write("package " + packageName + ";\n");
writer.write("import " + packageName + ".Binder;\n");
writer.write("import android.view.View;\n");
//2.写入-声明类及实现接口代码
writer.write("public class " + activityName + "_ViewBinding implements Binder<"
+ packageName + "." + activityName + ">{\n");
//3.写入-实现接口方法代码
writer.write(" @Override\n" +
" public void bind(final " + packageName + "." + activityName + " target) {\n");
//取出改Activity下所有@OnClick的Id
int[] clickViewIds = element.getAnnotation(OnClick.class).value();
//4.写入-setOnClickListener代码
for (int clickViewId : clickViewIds) {
writeSetOnClickListenerWithoutName(writer, packageName, activityName, clickViewId);
}
//5.补全类构造
writer.write(" \n }\n}\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (writer!=null){
writer.close();
writer = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 写入findViewById代码
* @param writer writer
* @param variableName 控件名
* @param typeMirror 控件类型
* @param variableId 控件Id
* @throws IOException 写入异常
*/
private void writeFindViewById(Writer writer, String variableName, TypeMirror typeMirror, int variableId) throws IOException {
writer.write(" target." + variableName + "=(" + typeMirror + ")target.findViewById(" + variableId + ");\n");
}
/**
* 已经调用过finfViewById()方法的控件设置点击事件
* @param writer writer
* @param variableName 控件名
* @throws IOException 写入异常
*/
private void writeSetOnClickListener(Writer writer, String packageName, String className, String variableName) throws IOException {
writer.write(" target." + variableName + ".setOnClickListener(new View.OnClickListener() {\n" +
" @Override\n" +
" public void onClick(View view) {\n" +
" try {\n" +
" target.onClick(view);\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
" }\n" +
" });\n");
}
/**
* 没有调用过findViewById()方法的控件设置点击事件
* @param writer writer
* @param viewId 控件Id
* @throws IOException 写入异常
*/
private void writeSetOnClickListenerWithoutName(Writer writer, String packageName, String className, int viewId) throws IOException {
writer.write(" target.findViewById(" + viewId + ")" + ".setOnClickListener(new View.OnClickListener() {\n" +
" @Override\n" +
" public void onClick(View view) {\n" +
" try {\n" +
" target.onClick(view);\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
" }\n" +
" });\n");
}
/**
* 判断数组中是否含有某个值
* 这里用来判断,所有设置@OnClick注解的控件Id中是否也同时设置了@BindView
* @param arr 某一个Activity下所有设置了@OnClick的控件Id
* @param arg 某一个Activity下设置了@BindView的控件Id
* @return true/false
*/
private boolean contains(int[] arr, int arg) {
boolean isContain = false;
if (arr.length == 0) {
isContain = false;
} else {
for (int i : arr) {
if (i == arg) {
isContain = true;
break;
}
}
}
return isContain;
}
module(app)新建一个ButterKnife类
在Activity中使用