Annotation 和 Annotation Processor
要了解Annotation Processor
,首先需要先了解什么是 Annotation
。
Annotation
: 是 Java 注解。 例如常见的 @Override
@Nullable
等, 可以对类或者字段进行标记。 这些标记可以在反射时读取 或者 通过 Annotation Processor
进行解析来自动生成一些对应的代码。
Annotation Processor
: 注解处理器, 在代码编译前进行处理。 可以自动生成一些代码,来避免在编码时写一些重复代码, 例如findViewByid()
使用Annotation Processor的一些库: butterknife Dagger2 ...
这里通过一个学习的例子来了解Annotation Processor 的工作原理。
示例Demo
- Android Studio创建一个java library (lib_annotation), 用于自定义注解
- New -> Java Class -> 类型选择 Annotation
代码语言:javascript复制BindView.java
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
/*
*@Retention 是一个元注释,表明我们自定义注释的使用范围
*/
public enum RetentionPolicy {
SOURCE, //注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
CLASS, //注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期,
RUNTIME; //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在, 运行时候可以配合反射一起使用
private RetentionPolicy() {
}
}
/*
* @Target定义注解的作用目标
*/
public enum ElementType {
TYPE, //可以用在class上
FIELD, //字段
METHOD, // 方法
PARAMETER, //方法参数
CONSTRUCTOR, //构造函数
LOCAL_VARIABLE, //局部变量
ANNOTATION_TYPE, //注解
PACKAGE, //包
TYPE_PARAMETER,
TYPE_USE;
private ElementType() {
}
}
- Android Studio再创建一个java library (lib_processor), 用于处理自定义注解
代码语言:javascript复制BindingProcessor.java
public class BindingProcessor extends AbstractProcessor {
Filer filer;
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
mMessager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE,"-------process start----");
/*roundEnv.getRootElements()会返回工程中所有的Class
在实际应用中需要对各个Class先做过滤以提高效率,避免对每个Class的内容都进行扫描*/
for (Element element : roundEnvironment.getRootElements()) {
// 获取包名
String packageStr = element.getEnclosingElement().toString();
// 获取类名称
String classStr = element.getSimpleName().toString();
mMessager.printMessage(Diagnostic.Kind.WARNING,"packageStr:" packageStr ", classStr:" classStr);
// 需要自动生成类的类名称
ClassName className = ClassName.get(packageStr, classStr "$Binding");
// 构造函数的builder
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(packageStr, classStr), "activity");
boolean hasBinding = false;
for (Element enclosedElement : element.getEnclosedElements()) {
BindView bindView = enclosedElement.getAnnotation(BindView.class);
if (bindView != null) {
hasBinding = true;
constructorBuilder.addStatement("activity.$N = activity.findViewById($L)",
enclosedElement.getSimpleName(), bindView.value());
}
}
TypeSpec builtClass = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.addMethod(constructorBuilder.build())
.build();
if (hasBinding) {
try {
JavaFile.builder(packageStr, builtClass)
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
}
- lib_processor 模块中向系统注册Annotation Process。
代码语言:javascript复制resources -> META-INF -> services -> javax.annotation.processing.Processor
com.******.BindingProcessor // 解析器的完整路径
- 自动生成的代码,可以通过反射进行调用。
代码语言:javascript复制BindViewUtils.java
public static void bind(Activity activity) {
try {
// new MainActivityBinding(activity);
Class bindingClass = Class.forName(activity.getClass().getCanonicalName() "$Binding");
Class activityClass = Class.forName(activity.getClass().getCanonicalName());
Constructor constructor = bindingClass.getDeclaredConstructor(activityClass);
constructor.newInstance(activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
可能遇到的问题
- compileJava 编译出现警告
> Task :lib_process:compileJava
警告: [options] 未与 -source 1.7 一起设置引导类路径
1 个警告
问题原因: 本机的jdk环境 与 工程的配置环境不匹配
解决方案:
- 查看本机的java 版本
$ /usr/libexec/java_home -V
Matching Java Virtual Machines (1):
1.8.0_191, x86_64: "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home
- 修改工程配置中的Java 环境配置
代码语言:javascript复制build.gradle
sourceCompatibility = "8"
targetCompatibility = "8"
- 注册processor 没有生效 向系统注册Annotation Process时,如果Processor 中process.java 未被执行。
resources -> META-INF -> services -> javax.annotation.processing.Processor
请检查文件路径是否正确, 本人就因为路径 META-INF
写成了 META_INF
导致注册失败。
END!