一、概念及作用
什么是APT?全称是Annotation Processing Tool,翻译过来就是注解处理工具,它的作用就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入,因此它能够使我们编写的代码更加优雅。目前很多优秀的第三方库就是使用APT的技术,比如butterknife、retrofit、enentBus等。
二、使用
1、在当前工程中创建注解的java library,命名为annotation
创建注解类,命名ARouter.java。
/**
* <pre>
* author : QB
* time : 2019/7/15
* version : v1.0.0
* desc : 注解文件,提供path参数
* </pre>
*/
@Target(ElementType.TYPE)// 该注解作用在类之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface ARouter {
String path();
}
2、在当前工程中创建注解处理器的java library,命名为compiler
(1)添加依赖
在当前library的build.gradle中添加如下依赖:
// AS3.4.1 + Gradle5.1.1 + auto-service:1.0-rc4
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.11.1'
// 引入annotation,处理@ARouter注解
implementation project(':annotation')
(2)创建注解处理器类
创建ARouterCompiler.java类,继承至AbstractProcessor,代码如下:
/**
* <pre>
* author : QB
* time : 2019/7/15
* version : v1.0.0
* desc : 采用javaPoet生成文件
* </pre>
*/
//使用AutoService生成注解处理器
@AutoService(Processor.class)
//当前注解类型
@SupportedAnnotationTypes({"com.xinyartech.annotation.ARouter"})
//JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//接收参数
@SupportedOptions("content")
public class ARouterCompiler extends AbstractProcessor {
//节点信息
private Elements elementUtils;
//文件生成器
private Filer mFiler;
//日志打印
private Messager mMessager;
private Types typeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mFiler = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
mMessager.printMessage(Diagnostic.Kind.NOTE, processingEnvironment.getOptions().get(
"content"));
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) return false;
//拿到所有被ARouter注解的类
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) {
//获取完整包名
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//获取类名
String className = element.getSimpleName().toString();
//打印当前类信息
mMessager.printMessage(Diagnostic.Kind.NOTE, "当前ARouter注解的类:" + className);
//最终编译生成的java文件
String finalClassName = className + "$$ARouter";
//拿到当前类的注解
ARouter aRouter = element.getAnnotation(ARouter.class);
String path = aRouter.path();
//采用拼接的方式生成注解文件
try {
//创建java文件 用于处理业务代码
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
//返回可操作java文件的对象
Writer writer = sourceFile.openWriter();
//设置包名
writer.write("package " + packageName + ";\n");
//设置类名
writer.write("public class " + finalClassName + " {\n");
//添加方法
writer.write("public static Class<?> findTargetClass(String path){\n");
//拿到类注解
ARouter aRouter = element.getAnnotation(ARouter.class);
//拿到当前注解的路径
String aRouterPath = aRouter.path();
//以下为方法逻辑
writer.write("if(path.equalsIgnoreCase(\"" + aRouterPath + "\")){\n");
writer.write("return " + className + ".class;\n}\n");
writer.write("return null;\n");
writer.write("}\n}");
//切记不要忘记关闭write
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
}
3、主项目app
(1)添加依赖
在app的build.gradle中添加如下依赖
//注解依赖
implementation project(path: ':annotation')
//依赖注解处理器
annotationProcessor project(':compiler')
如果需要传递参数给注解处理器,比如我们在注解处理器接受的key为"content",那么可以在app的build.gradle配置传递的值,代码如下:
defaultConfig {
...
// 在gradle文件中配置选项参数值(用于APT传参接收)
// 切记:必须写在defaultConfig节点下
javaCompileOptions {
annotationProcessorOptions {
arguments = [content : 'hello javaPoet']
}
}
}
切记,一定要在defaultConfig节点下配置。
(2)创建MainActivity.java
@ARouter(path = "/main/MainActivity")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void jump(View view) {
startActivity(new Intent(this, SecondActivity$$ARouter.getTargetClass("/main" +
"/SecondActivity")));
}
}
(3)创建SecondActivity.java
@ARouter(path = "/main/SecondActivity")
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
在类上添加ARouter注解,此时编译项目,会在build目录下生成目标文件。截图如下:
这样就可以像代码中那样实现跳转了。
二、JavaPoet
1、概念
JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。
2、使用
(1)需要在注解处理器的libary即compiler中build.config中加入依赖
//javaPoet依赖
implementation 'com.squareup:javapoet:1.11.1'
(2)修改ARouterCompiler.java类中代码生成方式
主要的代码在process()方法中。代码如下:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) return false;
//拿到所有被ARouter注解的类
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) {
//获取完整包名
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//获取类名
String className = element.getSimpleName().toString();
//打印当前类信息
mMessager.printMessage(Diagnostic.Kind.NOTE, "当前ARouter注解的类:" + className);
//最终编译生成的java文件
String finalClassName = className + "$$ARouter";
//拿到当前类的注解
ARouter aRouter = element.getAnnotation(ARouter.class);
String path = aRouter.path();
//文件处理器编写代码 ,采用javaPoet方式 先写方法,在写类,最后写包 method->class->package
//构建方法 $T:代表类 $S:代表字符串
MethodSpec methodSpec = MethodSpec.methodBuilder("getTargetClass")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Class.class)
.addParameter(String.class, "path")
.addStatement("return path.equalsIgnoreCase($S) ? " +
"$T.class : null", path, ClassName.get((TypeElement) element))
.build();
//构建类
TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(methodSpec)
.build();
//构建包
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.build();
//写入到文件生成器
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
重新编译,即可达到同样的效果。