美文网首页
27.APT技术与JavaPoet

27.APT技术与JavaPoet

作者: 任振铭 | 来源:发表于2021-01-24 09:43 被阅读0次

上一篇文章完成了组件化工程的搭建(https://www.jianshu.com/p/ed2c9f677e0e),这篇文章来说下APT和JavaPoet
下一篇我们会说下ARouter是如何实现页面跳转的(https://www.jianshu.com/p/0fa14358a765

APT

APT是英文Annotation Processing Tool的简写,是一种在编译期间通过对注解的处理,来实现自动生成辅助代码的技术。生成代码的方式有直接拼接字符串这种比较原始的方式,还有一种面向对象编程的方式-JavaPoet。使用APT技术的框架,通常都是属于编译期框架,如组件化框架ARouter,通信框架EventBus,以及使用非常频繁的ButterKnife,他们都会使用到APT并借助它来实现部分代码的生成,就等于借助编译器帮我们写代码一样。

JavaPoet

JavaPoet (https://github.com/square/javapoet)简单理解就是一个用来生成 .java源文件的Java API。我们通常写代码都是自己创建一个类,然后自己编写代码的实现,但是JavaPoet却是一个可以帮我们写java文件的库,由square开源。早期的APT技术生成辅助类的方式可以参考下EventBus中的实现,是通过拼接大量的字符串来生成一个文件,非常的繁琐,并且容易出错,JavaPoet的出现解决了这个问题,让我们以一种面向对象的方式生成java文件。实现方式可以参考ARouter源码。

如何使用

那么在Android开发中如何去使用APT技术,并结合JavaPoet去生成辅助类呢,其实很简单,今天的主要内容就是通过JavaPoet生成一个HelloWorld.java类

1.创建工程

我们创建一个Android项目,然后创建一个java library, apt技术需要在java library中创建相关类。然后让app模块依赖这个library,暂且命名为hello-world-compiler

然后引入需要的一些依赖,在java library模块的build.gradle文件的根目录配置如下

dependencies {
    //auto-service是Google开源的一个库,可以方便快捷的帮助我们进行组件化开发
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
    //引入javapoet
    implementation "com.squareup:javapoet:1.9.0"
}

既然是APT技术,那么必然少不了注解的,我们仿照开源框架的方式,再创建一个java library,命名为hello-world-annotion,然后在里边创建一个注解,就叫HelloWorld,然后在上边的hello-world-compiler中依赖它,这样,我们的注解处理器就可以找到这个注解了

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface HelloWorld {
    //这里也可以设置一些东西
}
dependencies {
    //auto-service是Google开源的一个库,可以方便快捷的帮助我们进行组件化开发
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
    //引入javapoet
    implementation "com.squareup:javapoet:1.9.0"
    implementation project(":hello-world-annotion")
}

那么当前三个module的依赖关系如下:
app

implementation project(":hello-world-annotation")
annotationProcessor project(":hello-world-compiler")

hello-world-compiler:

implementation project(":hello-world-annotation")

2.编写注解处理器

现在开始写注解处理器,创建一个类MyCompiler,MyCompiler继承AbstractProcessor,实现它的process方法

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)

我们上边知道,app以annotationProcessor的方式依赖了hello-world-compiler,所以当app模块编译的时候,process方法会被执行。或者如果有很多的module,每个module都依赖了注解处理器hello-world-compiler,那么每个module编译的时候,都会单独走一次process方法

除了这个必须实现的process方法之外,我们再重写AbstractProcessor的几个方法,他们的含义见注释,如下

@AutoService(Processor.class)
public class HelloWorldProcessor extends AbstractProcessor {
    // type(类信息)的工具类,包含用于操作TypeMirror的工具方法
    private Types typeUtils;
    // Message用来打印 日志相关信息
    private Messager messager;
    // 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
    private Filer filer;
    // 操作Element的工具类(类,函数,属性,其实都是Element)
    private Elements elementUtils;
    //从每个module接收到的数据
    private String moduleName;

    //此方法用于指定可以从module中接收的数据
    @Override
    public Set<String> getSupportedOptions() {
        return super.getSupportedOptions();
    }
    //此方法指明注解处理器支持的java sdk版本,建议和项目保持一致,否则编译时会有提示
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }
    //指明注解处理器可以处理哪些注解,如我们本次只有HelloWorld这个注解、
    //所以把它添加到set中并返回,表示我们只处理HelloWorld
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>();
        set.add(HelloWorld.class.getName());
        return set;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        elementUtils = processingEnvironment.getElementUtils();
        //如我们从app的build.gradle中配置了一个key为moduleName的值
        //在这里可以接收到
        moduleName = processingEnvironment.getOptions().get(“moduleName”);
    }
    ....
}

向注解处理器传递参数的配置方式

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                // project.getName() == app
                arguments = [moduleName: project.getName()]
            }
        }
    }
    ...
}

3.JavaPoet api

我们知道JavaPoet是用来写java文件的,一个java文件通常对应一个java类,那么java类中的元素在JavaPoet中都有所对应
方法 ---- MethodSpec
class ---- TypeSpec
java文件 ---- JavaFile

那么我们最终要生成一个这样的文件,代码该怎么写?

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

直接贴上process方法的实现

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.NOTE, "并没有发现 被@HelloWorld注解的地方呀");
            return false; // 没有机会处理
        }

        // 获取所有被 @ HelloWorld 注解的 元素集合
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(HelloWorld.class);
        // 遍历所有的类节点
        for (Element element : elements) {
            // 获取简单类名,例如:MainActivity
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被@ HelloWorld注解的类有:" + className); // 打印出 就证明APT没有问题
            // 1.方法
            MethodSpec mainMethod = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")
                    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                    .build();
            // 2.类
            TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(mainMethod)
                    .build();
            // 3.包
            JavaFile packagef = JavaFile.builder("com.renzhenming", helloWorld).build();
            // 去生成
            try {
                packagef.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
                messager.printMessage(Diagnostic.Kind.NOTE, "生成失败,请检查代码...");
            }  
        }
        return true; 表示处理注解完成
    }

这样一来,在app模块中使用HelloWorld注解某一个类,编译后就会在代码中生成一个HelloWorld的类了

参考代码:https://github.com/renzhenming/Modularization

相关文章

网友评论

      本文标题:27.APT技术与JavaPoet

      本文链接:https://www.haomeiwen.com/subject/nropzktx.html