前言 很高兴遇见你~
在本系列的上一篇文章中,我们对注解进行了讲解,还没有看过上一篇文章的朋友,建议先去阅读 Android APT 系列 (二):APT 筑基之注解 。至此,关于 Apt 基础部分我们都讲完了,接下来就正式进入 APT 技术的学习
Github Demo 地址 , 大家可以看 Demo 跟随我的思路一起分析
一、APT 介绍 1)、什么是 APT ? APT 全称 Annotation Processing Tool
,翻译过来即注解处理器。引用官方一段对 APT 的介绍:APT 是一种处理注释的工具, 它对源代码文件进行检测找出其中的注解,并使用注解进行额外的处理。
2)、APT 有什么用? APT 能在编译期根据编译阶段注解,给我们自动生成代码,简化使用。很多流行框架都使用到了 APT 技术,如 ButterKnife,Retrofit,Arouter,EventBus 等等
二、APT 工程 1)、APT 工程创建 一般情况下,APT 大致的的一个实现过程:
1、创建一个 Java Module
,用来编写注解
2、创建一个 Java Module
,用来读取注解信息,并根据指定规则,生成相应的类文件
3、创建一个 Android Module
,通过反射获取生成的类,进行合理的封装,提供给上层调用
如下图:
这是我的 APT 工程,关于 Module 名称可以任意取,按照我上面说的规则去进行就好了
2)、Module 依赖 工程创建好后,我们就需要理清楚各个 Module 之间的一个依赖关系:
1、因为 apt-processor
要读取 apt-annotation
的注解,所以 apt-processor
需要依赖 apt-annotation
1 2 3 4 dependencies { implementation project (path: ':apt-annotation' ) }
2、app 作为调用层,以上 3 个 Module 都需要进行依赖
1 2 3 4 5 6 7 dependencies { implementation project (path: ':apt-api' ) implementation project (path: ':apt-annotation' ) annotationProcessor project (path: ':apt-processor' ) }
APT 工程配置好之后,我们就可以对各个 Module 进行一个具体代码的编写了
三、apt-annotation 注解编写 这个 Module 的处理相对来说很简单,就是编写相应的自定义注解就好了,我编写的如下:
1 2 3 4 5 6 7 @Inherited @Documented @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE,ElementType.METHOD}) public @interface AptAnnotation { String desc () default "" ; }
四、apt-processor 自动生成代码 这个 Module 相对来说比较复杂,我们把它分为以下 3 个步骤:
1、注解处理器声明
2、注解处理器注册
3、注解处理器生成类文件
1)、注解处理器声明 1、新建一个类,类名按照自己的喜好取,继承 javax.annotation.processing
这个包下的 AbstractProcessor 类并实现其抽象方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class AptAnnotationProcessor extends AbstractProcessor { @Override public boolean process (Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false ; } }
重点看下第一个参数中的 TypeElement ,这个就涉及到 Element 的知识,我们简单的介绍一下:
Element 介绍
实际上,Java 源文件是一种结构体语言,源代码的每一个部分都对应了一个特定类型的 Element ,例如包,类,字段,方法等等:
1 2 3 4 5 6 7 8 9 package com.dream; public class Main <T> { private int x; public Main () { } }
Java 的 Element 是一个接口,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public interface Element extends javax .lang.model.AnnotatedConstruct { TypeMirror asType () ; ElementKind getKind () ; Set<Modifier> getModifiers () ; Name getSimpleName () ; Element getEnclosingElement () ; List<? extends Element > getEnclosedElements(); @Override boolean equals (Object obj) ; @Override int hashCode () ; @Override List<? extends AnnotationMirror > getAnnotationMirrors(); @Override <A extends Annotation > A getAnnotation (Class<A> annotationType) ; <R, P> R accept (ElementVisitor<R, P> v, P p) ; }
我们可以通过 Element 获取如上一些信息(写了注释的都是一些常用的)
由 Element 衍生出来的扩展类共有 5 种:
1、PackageElement 表示一个包程序元素
2、TypeElement 表示一个类或者接口程序元素
3、TypeParameterElement 表示一个泛型元素
4、VariableElement 表示一个字段、enum 常量、方法或者构造方法的参数、局部变量或异常参数
5、ExecuteableElement 表示某个类或者接口的方法、构造方法或初始化程序(静态或者实例)
可以发现,Element 有时会代表多种元素,例如 TypeElement 代表类或接口,此时我们可以通过 element.getKind() 来区分:
1 2 3 4 5 6 7 8 9 10 Set<? extends Element > elements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class);for (Element element : elements) { if (element.getKind() == ElementKind.CLASS) { } else if (element.getKind() == ElementKind.INTERFACE) { } }
ElementKind 是一个枚举类,它的取值有很多,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 PACKAGE ENUM CLASS ANNOTATION_TYPE INTERFACE ENUM_CONSTANT FIELD PARAMETER LOCAL_VARIABLE EXCEPTION_PARAMETER METHOD CONSTRUCTOR OTHER
关于 Element 就介绍到这,我们接着往下看
2、重写方法解读 除了必须实现的这个抽象方法,我们还可以重写其他 4 个常用的方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class AptAnnotationProcessor extends AbstractProcessor { private Elements mElementUtils; private Types mTypeUtils; private Filer mFiler; private Messager mMessager; @Override public synchronized void init (ProcessingEnvironment processingEnv) { super .init(processingEnv); mElementUtils = processingEnv.getElementUtils(); mTypeUtils = processingEnv.getTypeUtils(); mFiler = processingEnv.getFiler(); mMessager = processingEnv.getMessager(); } @Override public Set<String> getSupportedOptions () { return super .getSupportedOptions(); } @Override public Set<String> getSupportedAnnotationTypes () { return super .getSupportedAnnotationTypes(); } @Override public SourceVersion getSupportedSourceVersion () { return super .getSupportedSourceVersion(); } }
注意 :getSupportedAnnotationTypes()
、getSupportedSourceVersion()
和getSupportedOptions()
这三个方法,我们还可以采用注解的方式进行提供:
1 2 3 4 5 6 @SupportedOptions("MODULE_NAME") @SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class AptAnnotationProcessor extends AbstractProcessor { }
2)、注解处理器注册 注解处理器声明好了,下一步我们就要注册它,其中注册有两种方式:
1、手动注册
2、自动注册
手动注册比较繁琐固定且容易出错,不推荐使用,这里就不讲了。我们主要看下自动注册
自动注册 1、首先我们要在 apt-processor
这个 Module 下的 build.gradle 文件导入如下依赖:
1 2 implementation 'com.google.auto.service:auto-service:1.0-rc6' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
注意 :这两句必须都要加,否则注册不成功,我之前踩坑了
2、在注解处理器上加上 @AutoService(Processor.class)
即可完成注册
1 2 3 4 @AutoService(Processor.class) public class AptAnnotationProcessor extends AbstractProcessor { }
3)、注解处理器生成类文件 注册完成之后,我们就可以正式编写生成 Java 类文件的代码了,其中生成也有两种方式:
1、常规的写文件方式
2、通过 javapoet 框架来编写
1 的方式比较死板,需要把每一个字母都写上,不推荐使用,这里就不讲了。我们主要看下通过 javapoet 这个框架生成 Java 类文件
javapoet 方式 这种方式更加符合面向对象编码的一个风格,对 javapoet 还不熟的朋友,可以去 github 上学习一波 传送门 ,这里我们介绍一下它常用的一些类:
TypeSpec:用于生成类、接口、枚举对象的类
MethodSpec:用于生成方法对象的类
ParameterSpec:用于生成参数对象的类
AnnotationSpec:用于生成注解对象的类
FieldSpec:用于配置生成成员变量的类
ClassName:通过包名和类名生成的对象,在JavaPoet中相当于为其指定 Class
ParameterizedTypeName:通过 MainClass 和 IncludeClass 生成包含泛型的 Class
JavaFile:控制生成的 Java 文件的输出的类
1、导入 javapoet 框架依赖 1 implementation 'com.squareup:javapoet:1.13.0'
2、按照指定代码模版生成 Java 类文件 例如,我在 app 的 build.gradle 下进行了如下配置:
1 2 3 4 5 6 7 8 9 10 11 android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [MODULE_NAME: project.getName()] } } } }
在 MainActivity 下面进行了如下注解:
我希望生成的代码如下:
现在我们来实操一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 @AutoService(Processor.class) @SupportedOptions("MODULE_NAME") @SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class AptAnnotationProcessor extends AbstractProcessor { Filer filer; private String mModuleName; @Override public synchronized void init (ProcessingEnvironment processingEnvironment) { super .init(processingEnvironment); filer = processingEnvironment.getFiler(); mModuleName = processingEnv.getOptions().get("MODULE_NAME" ); } @Override public boolean process (Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (set == null || set.isEmpty()) { return false ; } Set<? extends Element > rootElements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class); MethodSpec.Builder builder = MethodSpec.methodBuilder("test" ) .addModifiers(Modifier.PUBLIC) .returns(void .class) .addParameter(String.class, "param" ); builder.addStatement("$T.out.println($S)" , System.class, "模块: " + mModuleName); if (rootElements != null && !rootElements.isEmpty()) { for (Element element : rootElements) { String elementName = element.getSimpleName().toString(); String desc = element.getAnnotation(AptAnnotation.class).desc(); builder.addStatement("$T.out.println($S)" , System.class, "节点: " + elementName + " " + "描述: " + desc); } } MethodSpec main = builder.build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld" ) .addModifiers(Modifier.PUBLIC) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder("com.dream.aptdemo" , helloWorld).build(); try { javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } return true ; } }
经过上面这些步骤,我们运行 App 就能生成上面截图的代码了,现在还差最后一步,对生成的代码进行使用
注意 :不同版本的 Gradle 生成的类文件位置可能不一样,我的 Gradle 版本是 6.7.1,生成的类文件在如下位置:
一些低版本的 Gradle 生成的类文件在 /build/generated/source
这个目录下
五、apt-api 调用生成代码完成业务功能 这个 Module 的操作相对来说也比较简单,就是通过反射获取到生成的类,进行相应的封装使用即可,我的编写如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyAptApi { @SuppressWarnings("all") public static void init () { try { Class c = Class.forName("com.dream.aptdemo.HelloWorld" ); Constructor declaredConstructor = c.getDeclaredConstructor(); Object o = declaredConstructor.newInstance(); Method test = c.getDeclaredMethod("test" , String.class); test.invoke(o, "" ); } catch (Exception e) { e.printStackTrace(); } } }
接着我们在 MainActivity 的 oncreate 方法里面进行调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @AptAnnotation(desc = "我是 MainActivity 上面的注解") public class MainActivity extends AppCompatActivity { @AptAnnotation(desc = "我是 onCreate 上面的注解") @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyAptApi.init(); } } 模块: app 节点: MainActivity 描述: 我是 MainActivity 上面的注解 节点: onCreate 描述: 我是 onCreate 上面的注解
六、总结 本篇文章讲的一些重点内容:
1、APT 工程所需创建的不同种类的 Module 及 Module 之间的依赖关系
2、Java 源文件实际上是一种结构体语言,源代码的每一个部分都对应了一个特定类型的 Element
3、采用 auto-service 对注解处理器进行自动注册
4、采用 javapoet 框架编写所需生成的 Java 类文件
5、通过反射及适当的封装,将生成的类的功能提供给上层调用
好了,本篇文章到这里就结束了,希望能给你带来帮助 🤝
感谢你阅读这篇文章
下篇预告 下篇文章我会讲我是如何应用 APT 技术实现反射创建 View 的一个替换,敬请期待吧😄
参考和推荐 Android注解处理器APT技术探究
你的点赞,评论,是对我巨大的鼓励!
欢迎关注我的公众号: sweetying ,文章更新可第一时间收到
如果有问题 ,公众号内有加我微信的入口,在技术学习、个人成长的道路上,我们一起前进!