简介
APT(Annotation Process Tool),是一种编译期间处理注解,按照一定规则生成代码的技术。
JavaPoet 由square公司开源的三方库,简化生成文件的过程。
使用过程
写一个BindView为例,来简单看一下自动生成文件过程。
工程创建
1.创建一个正常的Android工程,主模块为app,用于写Android业务
2.创建一个纯Java模块,命名为apt-annotation,用于定义注解
3.再创建一个纯Java模块,命名为apt-processor,用于解析注解,生成Java文件
gradle配置
app模块
dependencies {
//依赖上注解module
implementation project(":apt-annotation")
//依赖上注解module
annotationProcessor project(":apt-processor")
}
apt-annotation模块 无需特殊配置
apt-processor模块
dependencies {
//依赖上注解module
implementation project(":apt-annotation")
//谷歌提供的自动注册服务
implementation 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
//生成java代码库
implementation 'com.squareup:javapoet:1.10.0'
}
apt-annotation模块自定义注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
这里定义的BindView用于标记字段,值为控件ID
apt-processor模块实现自动生成代码的逻辑
定义一个xxxProcessor继承自AbstractProcessor,并为其添加@AutoService注解,如下
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Messager messager;
private Elements elementUtils;
private Types types;
private Filer filter;
private Map<String, List<NodeInfo>> mCatch = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filter = processingEnv.getFiler();
types = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(BindView.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set == null || roundEnvironment == null) {
return false;
}
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
if (elements == null || elements.isEmpty()) {
return false;
}
//遍历节点
for (Element element : elements) {
//获取节点包信息
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//获取节点类信息,由于@BindView作用于成员属性上,这里使用getEnclosingElement
String className = element.getEnclosingElement().getSimpleName().toString();
//获取节点标记的属性名称
String typeName = element.asType().toString();
//获取节点标记的属性名称
String nodeName = element.getSimpleName().toString();
//获取注解的值
int value = element.getAnnotation(BindView.class).value();
// 打印
messager.printMessage(Diagnostic.Kind.NOTE, "packageName = " + packageName);
messager.printMessage(Diagnostic.Kind.NOTE, "className = " + className);
messager.printMessage(Diagnostic.Kind.NOTE, "typeName = " + typeName);
messager.printMessage(Diagnostic.Kind.NOTE, "nodeName = " + nodeName);
messager.printMessage(Diagnostic.Kind.NOTE, "value = " + value);
//缓存key
String key = packageName + "." + className;
//缓存节点信息
List<NodeInfo> nodeInfos = mCatch.get(key);
if (nodeInfos == null) {
nodeInfos = new ArrayList<>();
nodeInfos.add(new NodeInfo(packageName, className, typeName, nodeName, value));
//缓存
mCatch.put(key, nodeInfos);
} else {
nodeInfos.add(new NodeInfo(packageName, className, typeName, nodeName, value));
}
}
if (!mCatch.isEmpty()) {
//遍历临时缓存文件
for (Map.Entry<String, List<NodeInfo>> entity : mCatch.entrySet()) {
try {
//创建文件
createFile(entity.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 创建文件过程
*
* @param nodeInfoList
* @return void
**/
private void createFile(List<NodeInfo> nodeInfoList) throws IOException {
NodeInfo nodeInfo = nodeInfoList.get(0);
// 生成文件的类名
String className = nodeInfo.getClassName() + "$ViewBinding";
//方法参数
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(nodeInfo.getPackageName(), nodeInfo.getClassName()), "target").build();
// 方法
MethodSpec.Builder methodSpecBuild = MethodSpec.methodBuilder("bind").addModifiers(Modifier.PUBLIC, Modifier.STATIC).addParameter(parameterSpec).returns(void.class);
//给方法添加代码块
for (NodeInfo info : nodeInfoList) {
methodSpecBuild.addStatement("target.$L = ($L)target.findViewById($L)",
info.getNodeName(),
info.getTypeName(),
info.getValue());
}
// 类
TypeSpec typeSpec = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuild.build())
.build();
//生成文件
JavaFile.builder(nodeInfo.getPackageName(), typeSpec)
.build()
.writeTo(filter);
}
}
项目会在编译的时候,扫描@AutoService注解,为其生成代码
app模块中使用
先定义一个ButterKnife
public class ButterKnife {
public static void bind(Activity target){
try {
Class<?> targetClass = target.getClass();
//获取生成的类
Class<?> bindViewClass = Class.forName(targetClass.getName() + "$ViewBinding");
//获取方法
Method method = bindViewClass.getMethod("bind", targetClass);
//执行方法
method.invoke(bindViewClass.newInstance(),target);
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后再activity中使用
public class HomeActivity extends AppCompatActivity {
@BindView(R.id.tv_title)
TextView tvTitle;
@BindView(R.id.tv_content)
TextView tvContent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
ButterKnife.bind(this);
tvTitle.setText("这是标题");
tvContent.setText("这是内容主体");
}
}
自动生成的文件为
public class HomeActivity$ViewBinding {
public static void bind(HomeActivity target) {
target.tvTitle = (android.widget.TextView)target.findViewById(2131231192);
target.tvContent = (android.widget.TextView)target.findViewById(2131231191);
}
}
自动生成的文件就是为了给Activity的字段赋值之用,因此需要在OnCreate方法的setContentView之后立刻调用。
至此,JavaPoet的简单使用过程圆满结束。
后记,刚开始学习这个的时候,在一个kotlin的工程里使用的,怎么也生成不了文件,后来查到,需要把 annotationProcessor project(":apt-processor") 改成 kapt project(":apt-processor") 参阅这里