一、APT的使用
用于APP在编译时期生成代码,要在AndroidStudio项目中创建Java Library工程。1.1 build.gradle源码
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//另外一个Java Library,用来生成Annotation
implementation project(path: ':annotations')
//注册一个APT功能
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
//有这个才能够在编译时期调用annotationProcessor
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
//javaPoet:使用oop思想生成代码
implementation 'com.squareup:javapoet:1.12.1'
}
// 防止java控制台输出中文乱码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "7"
targetCompatibility = "7"
1.2 AbstractProcessor派生类源码
package com.piaopiao.annonation_compiler;
import com.google.auto.service.AutoService;
import com.piaopiao.annotations.BindView;
import com.piaopiao.annotations.OnClick;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
* 用来生成衍生代码
*/
@AutoService(Processor.class)
//允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.BIND_VIEW, Constants.ON_CLICK})
//指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationsCompiler extends AbstractProcessor {
//1.日志打印工具类
private Messager messager;
//3.需要一个用来生成文件的对象
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
javaPoet(roundEnvironment);
// writerJava(roundEnvironment);
return true;
}
/**
* 手写java类
* @param roundEnv
*/
private void writerJava(RoundEnvironment roundEnv) {
//类 TypeElement
//方法 ExecutableElement
//属性 VariableElement
//需要开始进行分类
Map<String, List<Element>> mapElement = new HashMap<>();
//字段
Set<? extends Element> elementsAnnotatedWithField = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elementsAnnotatedWithField) {
VariableElement variableElement = (VariableElement) element;
//得到activity名字(全类名)
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
List<Element> variableElements = mapElement.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList();
mapElement.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
//方法
Set<? extends Element> elementsAnnotatedWithMethod = roundEnv.getElementsAnnotatedWith(OnClick.class);
for (Element element : elementsAnnotatedWithMethod) {
ExecutableElement executableElement = (ExecutableElement) element;
//得到activity名字(全类名)
String activityName = executableElement.getEnclosingElement().getSimpleName().toString();
//找到方法元素
List<Element> executableElements = mapElement.get(activityName);
if (executableElements == null) {
executableElements = new ArrayList();
mapElement.put(activityName, executableElements);
}
executableElements.add(executableElement);
}
if (mapElement.size() > 0) {
Writer writer = null;
Iterator<String> iterator = mapElement.keySet().iterator();
while (iterator.hasNext()) {
//开始生成对应的文件
String activityName = iterator.next();
List<Element> elements = mapElement.get(activityName);
//得到包名
TypeElement enclosingElment = (TypeElement) elements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElment).toString();
//写入文件
try {
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
//package com.example.butterknife_framework_demo;
writer.write("package " + packageName + ";\n");
//import com.example.butterknife_framework_demo.IBinder;
writer.write("import " + packageName + ".IBinder;\n");
//public class MainActivity_ViewBinding implements IBinder<com.example.butterknife_framework_demo.MainActivity> {
writer.write("public class " + activityName + "_ViewBinding implements IBinder<" + packageName + "." + activityName + ">{\n");
//@Override
writer.write("@Override\n");
//public void bind(com.example.butterknife_framework_demo.MainActivity target) {
writer.write("public void bind(final " + packageName + "." + activityName + " target){\n");
//target.textView = (android.widget.TextView) target.findViewById(2131165359);
for (Element element : elements) {
if (element instanceof VariableElement) {
//得到名字
String variableName = element.getSimpleName().toString();
//得到ID
int id = element.getAnnotation(BindView.class).value();
//得到类型
TypeMirror typeMirror = element.asType();
//target.textView = (android.widget.TextView) target.findViewById(2131165359);
writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
} else if (element instanceof ExecutableElement) {//方法
//方法名
String executableName = element.getSimpleName().toString();
int[] viewIds = element.getAnnotation(OnClick.class).value();
for (int id : viewIds) {
writer.write("target.findViewById(" + id + ").setOnClickListener( new android.view.View.OnClickListener(){\n");
// void onClick(View v);
writer.write("public void onClick(android.view.View view){\n");
writer.write("target." + executableName + "(view);");
writer.write("\n}\n});");
}
}
}
writer.write("\n}\n}");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 利用javaPoet生成java类
* @param roundEnv
*/
private void javaPoet(RoundEnvironment roundEnv) {
//类 TypeElement
//方法 ExecutableElement
//属性 VariableElement
//需要开始进行分类
Map<String, List<Element>> mapElement = new HashMap<>();
//字段
Set<? extends Element> elementsAnnotatedWithField = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elementsAnnotatedWithField) {
VariableElement variableElement = (VariableElement) element;
//得到activity名字(全类名)
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
List<Element> variableElements = mapElement.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList();
mapElement.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
//方法
Set<? extends Element> elementsAnnotatedWithMethod = roundEnv.getElementsAnnotatedWith(OnClick.class);
for (Element element : elementsAnnotatedWithMethod) {
ExecutableElement executableElement = (ExecutableElement) element;
//得到activity名字(类名)
String activityName = executableElement.getEnclosingElement().getSimpleName().toString();
//找到方法元素
List<Element> executableElements = mapElement.get(activityName);
if (executableElements == null) {
executableElements = new ArrayList();
mapElement.put(activityName, executableElements);
}
executableElements.add(executableElement);
}
//writerClass(mapElement);
if (mapElement.size() > 0) {
Iterator<String> iterator = mapElement.keySet().iterator();
while (iterator.hasNext()) {
//开始生成对应的文件
String activityName = iterator.next();
List<Element> elements = mapElement.get(activityName);
//得到包名
TypeElement enclosingElment = (TypeElement) elements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElment).toString();
MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(enclosingElment), "target", Modifier.FINAL)
.addAnnotation(Override.class)
.returns(void.class);
for (Element element : elements) {
if (element instanceof VariableElement) {
//得到名字
String variableName = element.getSimpleName().toString();
//得到ID
int id = element.getAnnotation(BindView.class).value();
//得到类型
TypeMirror typeMirror = element.asType();
methodSpecBuilder.addStatement("target.$N=($T)target.findViewById($L)", variableName, typeMirror, id);
} else if (element instanceof ExecutableElement) {//方法
//方法名
String executableName = element.getSimpleName().toString();
int[] viewIds = element.getAnnotation(OnClick.class).value();
for (int id : viewIds) {
// 匿名内部类实现的接口
ClassName viewClick = ClassName.get("android.view.View", "OnClickListener");
// 重写的方法
MethodSpec clickMethod = MethodSpec.methodBuilder("onClick")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(ClassName.get("android.view", "View"), "view")
.addStatement("target.$N(view)", executableName)
.build();
// 创建匿名内部类
TypeSpec typeClick = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(viewClick)
.addMethod(clickMethod)
.build();
methodSpecBuilder.addStatement("target.findViewById($L).setOnClickListener($L)", id, typeClick);
}
}
}
//创建方法
MethodSpec methodSpec = methodSpecBuilder.build();
//获取接口
ClassName iBinder = ClassName.get(packageName, "IBinder");
//获取对应的Activity
ClassName activityClassName = ClassName.get(packageName, activityName);
TypeSpec typeSpec = TypeSpec
.classBuilder(activityName + "_ViewBinding")
//添加泛型
.addSuperinterface(ParameterizedTypeName.get(iBinder, activityClassName))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpec).build();
JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
try {
//生成类
javaFile.writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "生成文件出现异常::" + e.getMessage());
}
}
}
}
}
二、简易ButterKnife的用法
package com.piaopiao.ymhbutterknife;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.piaopiao.annotations.BindView;
import com.piaopiao.annotations.OnClick;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView textView;
@BindView(R.id.tv1)
TextView textView1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YmhButterKnife.binder(this);
textView.setText("第一个点击事件");
textView1.setText("第二个点击事件");
}
@OnClick({R.id.tv, R.id.tv1})
public void onClick(View view) {
switch (view.getId()) {
case R.id.tv:
Toast.makeText(this, "第一个按钮", Toast.LENGTH_SHORT).show();
break;
case R.id.tv1:
Toast.makeText(this, "第二个按钮", Toast.LENGTH_SHORT).show();
break;
}
}
}
三、编译效果
![](https://i-blog.csdnimg.cn/blog_migrate/1da87fa02d6f588ac75820eca9aeeff1.png)
package com.piaopiao.ymhbutterknife;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import java.lang.Override;
public class MainActivity_ViewBinding implements IBinder<MainActivity> {
@Override
public void bind(final MainActivity target) {
target.textView=(TextView)target.findViewById(2131165325);
target.textView1=(TextView)target.findViewById(2131165326);
target.findViewById(2131165325).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
target.onClick(view);
}
});
target.findViewById(2131165326).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
target.onClick(view);
}
});
}
}
四、APT的调试
4.1 调试步骤
![](https://i-blog.csdnimg.cn/blog_migrate/b62738fc75c28067b6a75004b9053e0c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/fbf7aa28ef0bf27ede5780aa992e24fe.png)
![](https://i-blog.csdnimg.cn/blog_migrate/03c3886ba969a488898536ba3fe2bb48.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9239f15174c3594e97f0e65382f21e16.png)
4.2 注意事项
1. 如果按照上述步骤仍然无法进入调试模式,那么将所有模块的build目录都删除2. APT生成对应模块代码的时候应该把该模块的build目录删掉
推荐文章
JavaPoet的使用指南JavaPoet使用详解
JavaPoet的基本使用