JavaPoet 是 Square 公司推出的开源 Java代码生成框架,提供接口生成 Java 源文件。
它的项目主页及源码:https://github.com/square/javapoet
这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。
例如业内的一些开源库 Butterknife,GreenDao,Dagger 2 等等,也都有运用 JavaPoet 结合注解来实现编译时动态生成代码。
以下是 JavaPoet 里面的一些关键类:
JavaFile : 用于构造输出包含一个顶级类的Java文件
TypeSpec : 生成类,接口,或者枚举
MethodSpec : 生成构造函数或方法
FieldSpec : 生成成员变量或字段
ParameterSpec : 用来创建参数
AnnotationSpec : 用来创建注解
下面参考 Butterknife 源码,实现绑定 View 和设置监听的功能:
1,首先是 注解类:
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
int value();
}
2,继承 AbstractProcessor ,实现 Processor :
@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 代码 输出到 哪
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
// 拿到 每个类,要生成的 代码块;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndParseTargets(env);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成对应的 类文件;
ViewBindHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
private Map<TypeElement, List<CodeBlock.Builder>> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();
// 遍历带 对应注解的 元素,具体来看实际也就是 某个View对象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
ViewBindHelper.parseBindView(element, builderMap);
}
// 遍历带 对应注解的 元素,具体来看实际也就是 某个方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
ViewBindHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
下面来看几个关键性代码,ViewBindHelper 类的 parseBindView 方法:
public static void parseBindView(Element element, Map<TypeElement, List<CodeBlock.Builder>> codeBuilderMap) {
// 获取最里面的节点, 具体实际可能就是 某个Activity对象
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 这个view是哪个类 Class
String typeMirror = element.asType().toString();
// 注解的值,具体实际可能就是 R.id.xxx
int annotationValue = element.getAnnotation(BindView.class).value();
// 这个view对象 名称
String name = element.getSimpleName().toString();
//创建代码块
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", name); //$L是占位符,会把后面的 name 参数拼接到 $L 所在的地方
builder.add("($L)source.findViewById($L)", typeMirror, annotationValue);
List<CodeBlock.Builder> codeList = codeBuilderMap.get(enclosingElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(enclosingElement, codeList);
}
codeList.add(builder);
}
看到这里你就知道根据 BindView 注解怎么生成 findViewById 的代码了。OnClick注解也是类似。
最后看怎么生成Java文件,ViewBindHelper 类的 writeBindView 方法:
public static void writeBindView(TypeElement enclosingElement, List<CodeBlock.Builder> codeList, Filer filer) {
// enclosingElement ,暗指 某个Activity.
// 先拿到 Activity 所在包名
String packageName = enclosingElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));
// 再拿到 Activity 类名
String className = enclosingElement.getSimpleName().toString();
// 再拿到 Activity 是 哪个类
TypeName type = TypeName.get(enclosingElement.asType());//此元素定义的类型
if (type instanceof ParameterizedTypeName) {
type = ((ParameterizedTypeName) type).rawType;
}
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
// 创建构造方法 如果 Activity是 MainActivity,则会有 生成如下构造方法
// public MainActivity_ViewBinding(final MainActivity target, final View source) {
// target.btn1 = (android.widget.Button)source.findViewById(2131165217);
// source.findViewById(2131165217).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
// }
MethodSpec.Builder methodSpecBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(type, "target", Modifier.FINAL)
.addParameter(ClassName.get("android.view", "View"), "source", Modifier.FINAL);
for (CodeBlock.Builder codeBuilder : codeList) {
//方法里面 ,代码是什么
methodSpecBuilder.addStatement(codeBuilder.build());
}
methodSpecBuilder.build();
// 创建类 MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
// 将文件写出
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
而主工程代码,看 MainActivity 里面怎么使用的:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
public Button btn1;
@BindView(R.id.btn2)
public Button btn2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyAnnotationUtils.bind(this);
HelloWorld.main(null);
btn1.setText("测试成功1");
btn2.setText("测试成功2");
}
@OnClick({R.id.btn1, R.id.btn2})
public void onBtn1Click(View v) {
Toast.makeText(this, "测试" + ((Button)v).getText().toString(), Toast.LENGTH_SHORT).show();
}
}
通过上面,整个项目编译完成后,在 app\build\generated\source\apt\debug\com\mill\apt 目录下面有一个自动生成的类:
package com.mill.apt;
import android.view.View;
public class MainActivity_ViewBinding {
public MainActivity_ViewBinding(final MainActivity target, final View source) {
target.btn1 = (android.widget.Button)source.findViewById(2131165217);
target.btn2 = (android.widget.Button)source.findViewById(2131165218);
source.findViewById(2131165217).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
source.findViewById(2131165218).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
}
}
而我们关心的这个类里面的方法是怎么调用呢?那就要看 MyAnnotationUtils类的 bind 方法:
public class MyAnnotationUtils {
public static void bind(Activity activity) {
//获取activity的decorView(根view)
View view = activity.getWindow().getDecorView();
bind(activity, view);
}
private static void bind(Object obj, View view) {
String qualifiedName = obj.getClass().getName();
//找到该activity对应的Bind类的名字
String generateClass = qualifiedName + "_ViewBinding";
//然后调用Bind类的构造方法,从而完成activity里view的初始化
try {
Class.forName(generateClass).getConstructor(obj.getClass(), View.class).newInstance(obj, view);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
实际就是通过反射,调用动态生成类的构造方法。
这里提几个需要注意的点:
1,HelloProcessor 类是必须在 Java Module里面的,不然找不到 javax.annotation.processing.AbstractProcessor;
2,自Android Gradle 插件 2.2 版本开始,官方提供了名为 AnnotationProcessor 的功能来完全代替 Android-Apt;
3,建议使用 com.google.auto.service:auto-service 库,注册注解处理器;
导入库之后,类似 加上 @AutoService(Processor.class) ,就会自动生成对应的
\META-INF\services javax.annotation.processing.Processor:
@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
//......
}
最后附上demo地址:https://github.com/miLLlulei/AnnotationProcessor
欢迎大家star~