上一篇文章完成了组件化工程的搭建(https://www.jianshu.com/p/ed2c9f677e0e),这篇文章来说下APT和JavaPoet
下一篇我们会说下ARouter是如何实现页面跳转的(https://www.jianshu.com/p/0fa14358a765)
APT
APT是英文Annotation Processing Tool的简写,是一种在编译期间通过对注解的处理,来实现自动生成辅助代码的技术。生成代码的方式有直接拼接字符串这种比较原始的方式,还有一种面向对象编程的方式-JavaPoet。使用APT技术的框架,通常都是属于编译期框架,如组件化框架ARouter,通信框架EventBus,以及使用非常频繁的ButterKnife,他们都会使用到APT并借助它来实现部分代码的生成,就等于借助编译器帮我们写代码一样。
JavaPoet
JavaPoet (https://github.com/square/javapoet)简单理解就是一个用来生成 .java源文件的Java API。我们通常写代码都是自己创建一个类,然后自己编写代码的实现,但是JavaPoet却是一个可以帮我们写java文件的库,由square开源。早期的APT技术生成辅助类的方式可以参考下EventBus中的实现,是通过拼接大量的字符串来生成一个文件,非常的繁琐,并且容易出错,JavaPoet的出现解决了这个问题,让我们以一种面向对象的方式生成java文件。实现方式可以参考ARouter源码。
如何使用
那么在Android开发中如何去使用APT技术,并结合JavaPoet去生成辅助类呢,其实很简单,今天的主要内容就是通过JavaPoet生成一个HelloWorld.java类
1.创建工程
我们创建一个Android项目,然后创建一个java library, apt技术需要在java library中创建相关类。然后让app模块依赖这个library,暂且命名为hello-world-compiler
然后引入需要的一些依赖,在java library模块的build.gradle文件的根目录配置如下
dependencies {
//auto-service是Google开源的一个库,可以方便快捷的帮助我们进行组件化开发
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
//引入javapoet
implementation "com.squareup:javapoet:1.9.0"
}
既然是APT技术,那么必然少不了注解的,我们仿照开源框架的方式,再创建一个java library,命名为hello-world-annotion,然后在里边创建一个注解,就叫HelloWorld,然后在上边的hello-world-compiler中依赖它,这样,我们的注解处理器就可以找到这个注解了
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface HelloWorld {
//这里也可以设置一些东西
}
dependencies {
//auto-service是Google开源的一个库,可以方便快捷的帮助我们进行组件化开发
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
//引入javapoet
implementation "com.squareup:javapoet:1.9.0"
implementation project(":hello-world-annotion")
}
那么当前三个module的依赖关系如下:
app
implementation project(":hello-world-annotation")
annotationProcessor project(":hello-world-compiler")
hello-world-compiler:
implementation project(":hello-world-annotation")
2.编写注解处理器
现在开始写注解处理器,创建一个类MyCompiler,MyCompiler继承AbstractProcessor,实现它的process方法
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
我们上边知道,app以annotationProcessor的方式依赖了hello-world-compiler,所以当app模块编译的时候,process方法会被执行。或者如果有很多的module,每个module都依赖了注解处理器hello-world-compiler,那么每个module编译的时候,都会单独走一次process方法
除了这个必须实现的process方法之外,我们再重写AbstractProcessor的几个方法,他们的含义见注释,如下
@AutoService(Processor.class)
public class HelloWorldProcessor extends AbstractProcessor {
// type(类信息)的工具类,包含用于操作TypeMirror的工具方法
private Types typeUtils;
// Message用来打印 日志相关信息
private Messager messager;
// 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
private Filer filer;
// 操作Element的工具类(类,函数,属性,其实都是Element)
private Elements elementUtils;
//从每个module接收到的数据
private String moduleName;
//此方法用于指定可以从module中接收的数据
@Override
public Set<String> getSupportedOptions() {
return super.getSupportedOptions();
}
//此方法指明注解处理器支持的java sdk版本,建议和项目保持一致,否则编译时会有提示
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
//指明注解处理器可以处理哪些注解,如我们本次只有HelloWorld这个注解、
//所以把它添加到set中并返回,表示我们只处理HelloWorld
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new HashSet<>();
set.add(HelloWorld.class.getName());
return set;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
//如我们从app的build.gradle中配置了一个key为moduleName的值
//在这里可以接收到
moduleName = processingEnvironment.getOptions().get(“moduleName”);
}
....
}
向注解处理器传递参数的配置方式
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
// project.getName() == app
arguments = [moduleName: project.getName()]
}
}
}
...
}
3.JavaPoet api
我们知道JavaPoet是用来写java文件的,一个java文件通常对应一个java类,那么java类中的元素在JavaPoet中都有所对应
方法 ---- MethodSpec
class ---- TypeSpec
java文件 ---- JavaFile
那么我们最终要生成一个这样的文件,代码该怎么写?
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
直接贴上process方法的实现
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) {
messager.printMessage(Diagnostic.Kind.NOTE, "并没有发现 被@HelloWorld注解的地方呀");
return false; // 没有机会处理
}
// 获取所有被 @ HelloWorld 注解的 元素集合
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(HelloWorld.class);
// 遍历所有的类节点
for (Element element : elements) {
// 获取简单类名,例如:MainActivity
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被@ HelloWorld注解的类有:" + className); // 打印出 就证明APT没有问题
// 1.方法
MethodSpec mainMethod = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
// 2.类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(mainMethod)
.build();
// 3.包
JavaFile packagef = JavaFile.builder("com.renzhenming", helloWorld).build();
// 去生成
try {
packagef.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "生成失败,请检查代码...");
}
}
return true; 表示处理注解完成
}
这样一来,在app模块中使用HelloWorld注解某一个类,编译后就会在代码中生成一个HelloWorld的类了
网友评论