简介
当我们使用ROOM、Butterknife等的时候,我们都需要使用到注解,然后相应的三方框架就会在编译期为我们生成对应的代码,为我们生成代码的工具就称为注解处理器(annotation processing)。本篇博客就来介绍如何使用annotation processing为我们生成重复的代码片。
基本步骤
我们在使用Activity以及Fragment的时候,经常需要使用Intent、Bundle来传递数据,不过这样的代码往往都是重复的,本篇博客使用annotation processing来为我们自动完成这些重复代码。
1、创建Android Project。
2、创建annotation Module。这个Module类型为Java Library,仅仅包含需要的注解。
3、创建processor Module。这个Module类型为Java Library,需要依赖我们在第二步中创建的annotation Module,我们在这个Module中进行主要的注解处理以及生成代码,我们可以在这个Module里面使用任何的三方库,因为此Module仅仅在编译期有效,并不会打包到我们的apk中。
项目结构如下。
4、添加依赖
app需要添加依赖,annotationProcessor project(':inject-processor')说明inject-processor将被作为注解处理器,当编译app的时候则会调用inject-processor进行处理注解。
dependencies { implementation project(':inject-annotation') annotationProcessor project(':inject-processor') }
annotation Module不需要任何的依赖
processor Module需要依赖annotation Module
Annotation
首先我们在inject-annotation中定义一个注解
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface Inject { boolean optional() default false; }
@interface:说明是一个注解
@Retention:说明注解的生命期
RetentionPolicy.SOURCE —仅仅在源码级别,当被编译以后,注解被丢弃。
RetentionPolicy.CLASS —class级别,注解不会被加载到jvm中。
RetentionPolicy.RUNTIME — 应用级别,在运行时可以获取到的注解。
@Target:说明注解可以生效的目标
public enum ElementType { TYPE, //If you want to annotate class, interface, enum.. FIELD, //If you want to annotate field (includes enum constants) METHOD, //If you want to annotate method PARAMETER, //If you want to annotate parameter CONSTRUCTOR, //If you want to annotate constructor LOCAL_VARIABLE, //.. ANNOTATION_TYPE, //.. PACKAGE, //.. TYPE_PARAMETER, //..(java 8) TYPE_USE; //..(java 8) private ElementType() { } }
Processor
我们在inject-processor中定义一个AnnotationProcessor。我们自定义的AnnotationProcessor需要继承AbstractProcessor并实现process方法。
public class InjectProcess extends AbstractProcessor { //获取一些初始化工具 @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); } //处理注解 @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } //获取支持版本 @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } //获取支持的注解类型 @Override public Set<String> getSupportedAnnotationTypes() { Set<String> supportTypes = new HashSet<>(); supportTypes.add(Inject.class.getCanonicalName()); return supportTypes; } }
init(): 初始化,可以通过processingEnvironment参数获取到一些工具类,Filer(生成代码), Messager(打印日志), Elements(获取元素信息),Types可以获取类信息
Elements elementUtils = processingEnvironment.getElementUtils(); Filer filer = processingEnvironment.getFiler(); Types typeUtils = processingEnvironment.getTypeUtils(); Messager messager = processingEnvironment.getMessager(); //获取编译参数 Map<String, String> options = processingEnv.getOptions();
process():进行注解处理的位置,他的第一个参数为待处理的注解数量,第二个参数可以理解为当前的环境,也就是编译器为我们收集到的所有的注解信息。
getSupportedAnnotationTypes():返回支持的注解类型
getSupportedSourceVersion(): 我们一般返回最新的版本
对于后面两个方法,我们也可以使用注释代替。
@SupportedAnnotationTypes 指定此注解处理器支持的注解,可用 * 指定所有注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) @SupportedAnnotationTypes({"cn.inject.annotation.Inject"}) public class InjectProcess extends AbstractProcessor { }
注意:process()一般会回调多次,因为当第一次处理完毕以后,对于第一次生成的代码也需要扫描一次,如果没有对应的注解,则结束,如果还有,则重复前面的操作。所以我们需要根据process的第一个参数来判断。
然后为了让编译器知道我们的注解处理器在哪里,我们需要在主目录下面创建resources文件夹,然后里面创建META-INF文件夹,然后创建services文件夹,再创建javax.annotation.processing.Processor,然后写入AnnotationProcessor的绝对路径。
获取参数
很多三方框架,比如ARouter等会传递参数给注解处理器,下面介绍如何传递参数
定义参数
defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = ["key1": "value", "key2": "enable"] } } }
定义的参数只能是String:String类型
获取参数
前面已经提到了,在init()方法中,我们可以通过ProcessingEnvironment参数获取到一些工具类,同样的,我们也可以获取到参数
Map<String, String> options = processingEnvironment.getOptions(); if (options != null || options.size() > 0) { Log.i(options.toString()); }
工具类的用法
Messager:一般用来打印日志
processingEnvironment.getMessager()
用法
public class Log { private Messager mMessager; public Log(Messager messager) { mMessager = messager; } public void i(String msg) { mMessager.printMessage(Diagnostic.Kind.OTHER, msg); } public void w(String msg) { mMessager.printMessage(Diagnostic.Kind.WARNING, msg); } public void e(String msg) { mMessager.printMessage(Diagnostic.Kind.ERROR, msg); } }
Filer:用于生成Java文件,一般是用来和JavaPoet一同使用,单独也可以。
Filer filer = processingEnvironment.getFiler(); JavaFile javaFile = JavaFile.builder("com.inject.helper", injectHelper.build()) .addFileComment("create by Inject") .build(); javaFile.writeTo(filer);
Types:获取类型相关
Types typeUtils = processingEnvironment.getTypeUtils(); //获取对应类型的直接父类(可以递归调用) List<? extends TypeMirror> typeMirrors = typeUtils.directSupertypes(typeMirror); //判断一个类型是否为Activity private boolean isActivity(TypeMirror typeMirror) { List<? extends TypeMirror> typeMirrors = typeUtils.directSupertypes(typeMirror); for (TypeMirror mirror : typeMirrors) { if (CommonClass.activityName.equals(mirror.toString())) { return true; } if (isActivity(mirror)) { return true; } } return false; } //判断A是否为B子类 typeUtils.isSubtype(A,B);
Elements:元素相关
Elements elementUtils = processingEnvironment.getElementUtils(); //获取Activity元素 elements.getTypeElement("android.app.Activity"); //判断是否是Activity private boolean isActivity(TypeMirror typeMirror) { TypeMirror activityTypeMirror = elementUtils.getTypeElement("android.app.Activity").asType(); return typeUtils.isSubtype(typeMirror, activityTypeMirror); }
Element
前面已经介绍了,对于注解的处理主要是在process方法中,我们可以通过roundEnvironment.getElementsAnnotatedWith获取到拥有指定注解的元素,我们可以看到,元素一般使用Element表示。所以在真正开始编码之前,我们需要对Element有一个基础的认识。
//处理注解 @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //获取拥有指定注解的元素 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Inject.class); return false; }
Element:这个是一个接口,其子类有5个,分别如下。
package com.example; // PackageElement public class Foo { // TypeElement private int a; // VariableElement private Foo other; // VariableElement public Foo () {} // ExecuteableElement public void setA ( // ExecuteableElement int newA // TypeParameterElement ) {} }
Tables | Are |
TypeElement | 一个类或接口 |
VariableElement | 一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数 |
ExecutableElement | 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素 |
PackageElement | 一个包程序元素 |
TypeParameterElement | 一般类、接口、方法或构造方法元素的泛型参数 |
下面分别介绍其方法
public interface Element extends AnnotatedConstruct { TypeMirror asType(); ElementKind getKind(); Set<Modifier> getModifiers(); Name getSimpleName(); Element getEnclosingElement(); List<? extends Element> getEnclosedElements(); boolean equals(Object var1); int hashCode(); List<? extends AnnotationMirror> getAnnotationMirrors(); <A extends Annotation> A getAnnotation(Class<A> var1); <R, P> R accept(ElementVisitor<R, P> var1, P var2); }
可看出其实Element是定义的一个接口,定义了外部调用暴露出的接口
方法 | 解释 |
---|---|
asType | 返回此元素的类型,用TypeMirror表示,可以获知Element的数据类型 |
getKind | 返回此元素的种类:包、类、接口、方法、字段。 |
getModifiers | 返回此元素的修饰符 |
getSimpleName | 返回此元素的简单名称,比如activity名 |
getEnclosingElement | 返回封装此元素的外层元素,如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素; 如果此元素是顶层类型,则返回它的包如果此元素是一个包,则返回 null; 如果此元素是一个泛型参数,则返回 null. |
getAnnotation | 返回此元素针对指定类型的注解(如果存在这样的注解),否则返回 null。注解可以是继承的,也可以是直接存在于此元素上的 |
五个子类各有各的用处并且有各种独立的方法,在使用的时候可以强制将Element对象转换成其中的任一一种,但是我们需要判断其的类型。
其中最核心的两个子分别是TypeElement和VariableElement
TypeElement
public interface TypeElement extends Element, Parameterizable, QualifiedNameable { //返回封装此元素的所有外层元素 List<? extends Element> getEnclosedElements(); //返回此类型元素的嵌套种类 NestingKind getNestingKind(); //返回此类型元素的完全限定名称。包名+类名 Name getQualifiedName(); //返回类名 Name getSimpleName(); //获取父类类型 TypeMirror getSuperclass(); //获取其实现的接口 List<? extends TypeMirror> getInterfaces(); //获取参数 List<? extends TypeParameterElement> getTypeParameters(); //获取外层元素 Element getEnclosingElement(); }
VariableElement
public interface VariableElement extends Element { //获取值 Object getConstantValue(); //获取类名 Name getSimpleName(); Element getEnclosingElement(); }
编码
当我们知道了每种Element的常用方法,那么我们就可以使用Filer去生成Java代码,不过推荐使用JavaPoet,相关资料可以看这里。一般来说,编写代码需要两步,第一步,收集所有的注解,第二步,生成代码。生成的代码在app\build\generated\source\apt\debug\下面可以找到。
在process方法中,我们可以通过第一个参数来判断是否需要收集,然后按照业务逻辑存储各个Element,最后使用JavaPoet去生成代码。附上本文博客所说的demo。目前只包含基础数据类型以及String,Serializable,Parcelable。对于其他复合对象,暂时不支持,最新版本可以查看github上面的说明文档。
参考链接:
Annotation Processing : Don’t Repeat Yourself, Generate Your Code.
The 10-Step Guide to Annotation Processing in Android Studio
Github地址: