Android APT 系列 (三):APT 技能探究

源码 2024-10-8 08:28:57 99 0 来自 中国
APT 先容

什么是 APT ?

APT 全称 Annotation Processing Tool,翻译过来即注解处置惩罚器。引用官方一段对 APT 的先容:APT 是一种处置惩罚表明的工具, 它对源代码文件举行检测找出此中的注解,并使用注解举行额外的处置惩罚。
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
//apt-processor 的 build.gradle 文件dependencies {    implementation project(path: ':apt-annotation')}2、app 作为调用层,以上 3 个 Module都必要举行依靠
//app 的 build.gradle 文件dependencies {    //...    implementation project(path: ':apt-api')    implementation project(path: ':apt-annotation')    annotationProcessor project(path: ':apt-processor')}APT工程设置好之后,我们就可以对各个 Module 举行一个具体代码的编写了
apt-annotation 注解编写

这个 Module的处置惩罚相对来说很简单,就是编写相应的自界说注解就好了,我编写的如下:
@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类并实现其抽象方法
public class AptAnnotationProcessor extends AbstractProcessor {      /**     * 编写天生 Java 类的干系逻辑     *     * @param set              支持处置惩罚的注解聚集     * @param roundEnvironment 通过该对象查找指定注解下的节点信息     * @return true: 表现注解已处置惩罚,后续注解处置惩罚器无需再处置惩罚它们;false: 表现注解未处置惩罚,大概要求后续注解处置惩罚器处置惩罚     */    @Override    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {        return false;    }}重点看下第一个参数中的 TypeElement ,这个就涉及到Element 的知识,我们简单的先容一下:
Element 先容
现实上,Java 源文件是一种布局体语言,源代码的每一个部门都对应了一个特定范例的 Element,比方包,类,字段,方法等等:
package com.dream;         // PackageElement:包元素public class Main<T> {     // TypeElement:类元素; 此中 <T> 属于 TypeParameterElement 泛型元素    private int x;         // VariableElement:变量、罗列、方法参数元素    public Main() {        // ExecuteableElement:构造函数、方法元素    }}Java 的 Element是一个接口,源码如下:
public interface Element extends javax.lang.model.AnnotatedConstruct {    // 获取元素的范例,现实的对象范例    TypeMirror asType();    // 获取Element的范例,判定是哪种Element    ElementKind getKind();    // 获取修饰符,如public static final等关键字    Set<Modifier> getModifiers();    // 获取类名    Name getSimpleName();    // 返回包罗该节点的父节点,与getEnclosedElements()方法相反    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、ExecutableElement表现某个类大概接口的方法、构造方法或初始化程序(静态大概实例)
可以发现,Element 有时会代表多种元素,比方 TypeElement代表类或接口,此时我们可以通过 element.getKind()来区分:
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class);for (Element element : elements) {    if (element.getKind() == ElementKind.CLASS) {        // 假如元素是类    } else if (element.getKind() == ElementKind.INTERFACE) {        // 假如元素是接口    }}ElementKind是一个罗列类,它的取值有很多,如下:
PACKAGE //表现包ENUM //表现罗列CLASS //表现类ANNOTATION_TYPE //表现注解INTERFACE //表现接口ENUM_CONSTANT //表现罗列常量FIELD //表现字段PARAMETER //表现参数LOCAL_VARIABLE //表现本地变量EXCEPTION_PARAMETER //表现非常参数METHOD //表现方法CONSTRUCTOR //表现构造函数OTHER //表现其他关于Element就先容到这,我们接着往下看
2、重写方法解读
除了必须实现的这个抽象方法,我们还可以重写其他 4 个常用的方法,如下:
public class AptAnnotationProcessor extends AbstractProcessor {    //...      /**      * 节点工具类(类、函数、属性都是节点)     */    private Elements mElementUtils;    /**      * 类信息工具类     */    private Types mTypeUtils;    /**     * 文件天生器     */    private Filer mFiler;    /**     * 日记信息打印器     */    private Messager mMessager;      /**     * 做一些初始化的工作     *      * @param processingEnvironment 这个参数提供了多少工具类,供编写天生 Java 类时所使用     */    @Override    public synchronized void init(ProcessingEnvironment processingEnv) {        super.init(processingEnv);        mElementUtils = processingEnv.getElementUtils();        mTypeUtils = processingEnv.getTypeUtils();        mFiler = processingEnv.getFiler();        mMessager = processingEnv.getMessager();    }      /**     * 吸收外来传入的参数,最常用的情势就是在 build.gradle 脚本文件里的 javaCompileOptions 的设置     *     * @return 属性的 Key 聚集     */    @Override    public Set<String> getSupportedOptions() {        return super.getSupportedOptions();    }    /**     * 当前注解处置惩罚器支持的注解聚集,假如支持,就会调用 process 方法     *     * @return 支持的注解聚集     */    @Override    public Set<String> getSupportedAnnotationTypes() {        return super.getSupportedAnnotationTypes();    }            /**     * 编译当前注解处置惩罚器的 JDK 版本     *     * @return JDK 版本     */    @Override    public SourceVersion getSupportedSourceVersion() {        return super.getSupportedSourceVersion();    }}注意:getSupportedAnnotationTypes()、getSupportedSourceVersion()和getSupportedOptions()这三个方法,我们还可以接纳注解的方式举行提供:
@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 文件导入如下依靠:
implementation 'com.google.auto.service:auto-service:1.0-rc6'annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'注意:这两句必须都要加,否则注册不乐成,我之前踩坑了
2、在注解处置惩罚器上加上 @AutoService(Processor.class) 即可完成注册
@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 框架依靠

implementation 'com.squareup:javapoet:1.13.0'2、按照指定代码模版天生 Java 类文件

比方,我在app的build.gradle下举行了如下设置:
android {    //...    defaultConfig {        //...        javaCompileOptions {            annotationProcessorOptions {                arguments = [MODULE_NAME: project.getName()]            }        }    }}在 MainActivity下面举行了如下注解:
@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();    }}@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();    }}我希望天生的代码如下:
public class HelloWorld {  public void test(String param) {    System.out.println("模块: apt-app");    System.out.println("节点: MainActivity  描述: 我是 MainActivity 上面的注解");    System.out.println("节点: onCreate  描述: 我是 onCreate 上面的注解");  }}现在我们来实操一下:
@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();        //通过 key 获取 build.gradle 中对应的 value        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);        // 构建 test 函数        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();        // 构建 HelloWorld 类        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,天生的类文件在如下位置:

2.png
一些低版本的 Gradle 天生的类文件在 /build/generated/source这个目次下
apt-api 调用天生代码完成业务功能

这个Module的操作相对来说也比力简单,就是通过反射获取到天生的类,举行相应的封装使用即可,我的编写如下:
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 方法内里举行调用:
@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、通过反射及得当的封装,将天生的类的功能提供给上层调用
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 14:21, Processed in 0.149756 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表