通过以上的分析我们就可以明确以下 极简 步骤:
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' compileOnly 'com.google.auto.service:auto-service:1.0-rc4' 复制代码
顺手Sync下
module之间相互依赖
主 module(app) 依赖
implementation project(path: ':annotations') annotationProcessor project(path: ':annotation_compiler') 复制代码
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(); } 复制代码
/** * 声明注解处理器要处理的注解 */ @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<String, List<VariableElement>> mapBindView = new HashMap<>(); for (Element element : elementsAnnotatedWith) { //获取控件元素 VariableElement variableElement = (VariableElement) element; //获取控件元素的父元素Name(activity或fragment) String activityName = variableElement.getEnclosingElement().getSimpleName().toString(); List<VariableElement> 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<String, Element> 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<VariableElement> 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<Integer> 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; } 复制代码