概述
本文记录使用 javapoet 以及 auto-service 进行编译时注解的过程以及注意点.
最近又使用了一次编译时注解,期间产生了不少问题.
术语的解释
Element
这个代表被注解的元素.这个类有个很重要的方法,getEnclosingElement:这方法的含义是获取 包裹 element 最外围的元素.比如类的最外围的元素是 package.
1
|
PackageElement pkgElement = (PackageElement) element.getEnclosingElement();
|
其他方法都很简单.
javapoet 库中一些重要的接口和方法
- TypeName: 对应了 java 代码中的一个类型元素,常用于声明一个方法参数,还有一些 collection 范型使用.
1
2
|
// 用来定一个 ComponentInfo 类的元素类型
TypeName mComponentInfoClassName = ClassName.get(ComponentInfo.class);
|
- ParameterizedTypeName 用来声明一个方法的参数.有个 get 方法,这个方法第一个参数是声明原生的类型,后面一个可变参数,声明第一个参数的参数.
1
2
3
4
5
6
7
8
|
// 声明一个参数的类型是 Map<String,List<ComponentInfo>>
ParameterizedTypeName paramListComponent = ParameterizedTypeName.get(ClassName.get(List.class), mComponentInfoClassName);
ParameterizedTypeName moduleLoaderParameter = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
paramListComponent
);
|
- ParameterSpec
这个类代表方法的参数
1
2
|
// 声明一个方法的参数是:final Map<String,List<ComponentInfo>> targetMap.
ParameterSpec injectParameterSpec = ParameterSpec.builder(moduleLoaderParameter, "targetMap", Modifier.FINAL).build();
|
- MethodSpec.Builder
方法的构造器,用来构造一个方法.
1
2
3
4
5
6
7
8
|
// 用来构造一个私有方法,名字叫 inject ,参数是 Map<String,List<ComponentInfo>> targetMap,String group,String pkg,String name,int type
MethodSpec.Builder injectElementBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PRIVATE)
.addParameter(injectParameterSpec)
.addParameter(String.class, "group")
.addParameter(String.class, "pkg")
.addParameter(String.class, "name")
.addParameter(int.class, "type");
|
这个构造还可以继续添加语句
1
2
3
4
5
6
7
8
9
10
11
12
|
injectElementBuilder.addStatement("List<$T> list = targetMap.get(name)", ComponentInfo.class)
.beginControlFlow("if( list == null )")
.addStatement("list = new $T<>()", ArrayList.class)
.addStatement("targetMap.put(group, list)")
.endControlFlow()
.addStatement("ComponentInfo info = new ComponentInfo(type, group, pkg, name)")
.beginControlFlow("try")
.addStatement(" info.setClazz(Class.forName(pkg + name))")
.nextControlFlow("catch(Exception e)")
.addStatement("e.printStackTrace()")
.endControlFlow()
.addStatement("list.add(info)");
|
注意其中的两个 ControlFlow .
- TypeSpec
用来声明一个类的描述
1
2
3
4
5
6
|
// 声明一个类,类名是 className,里面有两个方法
TypeSpec typeSpec = TypeSpec.classBuilder("className")
.addModifiers(Modifier.PUBLIC)
.addMethod(injectElementMethod)
.addMethod(injectMethodSpec)
.build();
|
- JavaFile
代表一个输出的 java 文件.
1
2
|
// 声明一个包名为 : com.steve.pkg 的java 文件.文件描述用的是一个 TypeSpec
JavaFile javaFile = JavaFile.builder("com.steve.pkg", typeSpec).build();
|
使用的注意事项
- 新版的 studio 以及不需要 android-apt
- 对于注解器的依赖可以通过 annotationProcessor project(’:processor’) 来对注解器工程进行依赖
- 对于注解配置选项可以在 gradle 中进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
|
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
|
- 对于注解器的注册,@AutoService(Processor.class),不要写错了,写成 process
- 注解器的配置项需要在注解器中声明 @SupportedOptions(“moduleName”)
- 注解器中的 log 不要随便打 error,不然就会停止解析,这点和 android 中的 log 有些差异.