组件化 APT JavaPoet

一、APT

apt:注解处理器,通俗来讲:根据规则,帮我们生成代码、类文件。

分module来实现apt。分别为:

  • 注解Moudle Java Library
  • 注解处理器Module Java Library
  • 主module Android Module

1. 自定义注解:

  • 新建java library,并创建注解类ARouter
    /**
     * <strong>Activity使用的布局文件注解</strong>
     * <ul>
     * <li>@Target(ElementType.TYPE)   // 接口、类、枚举、注解</li>
     * <li>@Target(ElementType.FIELD) // 属性、枚举的常量</li>
     * <li>@Target(ElementType.METHOD) // 方法</li>
     * <li>@Target(ElementType.PARAMETER) // 方法参数</li>
     * <li>@Target(ElementType.CONSTRUCTOR)  // 构造函数</li>
     * <li>@Target(ElementType.LOCAL_VARIABLE)// 局部变量</li>
     * <li>@Target(ElementType.ANNOTATION_TYPE)// 该注解使用在另一个注解上</li>
     * <li>@Target(ElementType.PACKAGE) // 包</li>
     * <li>@Retention(RetentionPolicy.RUNTIME) <br>注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容</li>
     * </ul>
     * <p>
     * 生命周期:SOURCE < CLASS < RUNTIME
     * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
     * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
     * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
     */
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface ARouter {
        // 详细路由路径(必填),如 "/app/MainActivity"
        String path();
    
        // 可以不填写,可以从path中拿
        String group() default "";
    }
    

2. 自定义注解处理器

  • 新建java library,并配置.gradle文件。注意在gradle文件中高低版本的适配。

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        // 注册注解,并对其生成META-INF的配置信息,rc2在gradle5.0后有坑
        // As-3.2.1 + gradle4.10.1-all + auto-service:1.0-rc2
        // implementation 'com.google.auto.service:auto-service:1.0-rc2'
    
        // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
        compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
        annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    
        // 引入注解,让注解处理器,处理注解
        implementation project(':annotation')
    }
    
    // java控制台输出中文乱码
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
    
    // jdk编译版本1.7
    sourceCompatibility = "7"
    targetCompatibility = "7"
    
  • 自定义注解处理器类

    // AutoService则是固定的写法,加个注解即可
    // 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
    // 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
    @AutoService(Processor.class)
    
    // 允许/支持的注解类型,让注解处理器处理(新增annotation module),支持多个
    @SupportedAnnotationTypes(value = {"com.purang.annotation.ARouter"})
    // 指定JDK编译版本
    @SupportedSourceVersion(value = SourceVersion.RELEASE_7)
    // 注解处理器接收的参数
    @SupportedOptions(value = "content")
    public class ARouterProcessor extends AbstractProcessor {
        // 操作Element的工具类 , 类、函数、属性都是Element
        private Elements mElementUtils;
    
        // Type (类信息)的工具类,包含用于操作TypeMirror的工具方法
        private Types mTypesUtils;
    
        // Messager用来报告错误,警告和其他提示信息
        private Messager mMessager;
    
        // 文件生成器  类/资源,Filter用来创建新的源文件,class文件以及辅助文件
        private Filer mFiler;
    
    
        // 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            // 父类受保护属性,可以直接拿来使用。
            // 其实就是init方法的参数ProcessingEnvironment
            // processingEnv.getMessager(); //参考源码64行
            mElementUtils = processingEnvironment.getElementUtils();
            mTypesUtils = processingEnvironment.getTypeUtils();
            mMessager = processingEnvironment.getMessager();
            mFiler = processingEnvironment.getFiler();
    
            // 通过processingEnvironment去获取build.gradle传过来的参数
            String content = processingEnvironment.getOptions().get("content");
            // 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e那么好使
            mMessager.printMessage(Diagnostic.Kind.NOTE, content);
    
        }
    
        /**
         * 相当于main函数,开始处理注解
         * 注解处理器的核心方法,处理具体的注解,生成Java文件
         *
         * @param set              使用了支持处理注解的节点集合(类 上面写了注解)
         * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解。
         * @return true 表示后续处理器不会再处理(已经处理完成)
         */
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
            if (set.isEmpty()) return false;
    
            // 获取所有带ARouter注解的 类节点
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            for (Element element : elements) {
                // 通过类节点获取包节点(全路径:com.netease.xxx)
                String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
                // 获取简单类名
                String className = element.getSimpleName().toString();
                mMessager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
                // 最终想生成的类文件名
                String finalClassName = className + "$$ARouter";
                // 公开课写法,也是EventBus写法(https://github.com/greenrobot/EventBus)
                try {
                    // 创建一个新的源文件(Class),并返回一个对象以允许写入它
                    JavaFileObject sourceFile = mFiler.createSourceFile(packageName + "." + finalClassName);
                    // 定义Writer对象,开启写入
                    Writer writer = sourceFile.openWriter();
                    // 设置包名
                    writer.write("package " + packageName + ";\n\n");
                    writer.write("public class " + finalClassName + "{\n");
                    writer.write("public static Class<?> findTargetClass(String path){\n");
                    // 获取类之上@ARouter注解的path值
                    ARouter annotation = element.getAnnotation(ARouter.class);
                    writer.write("if(path.equalsIgnoreCase(\"" + annotation.path() + "\")){\n");
                    writer.write("return " + className + ".class;}\n");
                    writer.write("return null;\n");
                    writer.write("}\n}");
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
    }
    

3. 在主module依赖注解处理器:

  • 依赖注解及注解处理器

    	// 依赖注解
        implementation project(':annotation')
        // 依赖注解处理器
        annotationProcessor project(':compiler')
    
  • 在主module的 build.gradle中做一下配置,用于给APT传参
    defaultConfig {

      // 在gradle文件中配置选项参数值(用于APT传参接收)
      // 切记:必须写在defaultConfig节点下
      javaCompileOptions {
          annotationProcessorOptions {
              arguments = [content : 'hello apt']
          }
      }
    

    }

  • 使用注解

      @ARouter(path = "/app/OrderActivity")
      public class MainActivity extends AppCompatActivity {
      		// ...
      }
    
  • 执行命令Clean Project ,Make Project 查看控制台:
    在这里插入图片描述

  • 主module 的build 目录下 查看按照规则生成的代码:
    在这里插入图片描述
    可以看到分别在\..\generated\..和\intermediates\javac\..\目录下生成了java文件和class文件 MainActivity$$ARouter ,前者可编辑。

  • 执行命令Build APK(s)打包生成apk,查看该文件是否在apk中:
    在这里插入图片描述

  • 随便创建几个类,分别添加ARouter注解,执行 clean 、 make命令:
    在这里插入图片描述
    在这里插入图片描述

  • 解释一下在注解处理器 module下 ARouterProcessor文件中@AutoService(Processor.class)注解的作用(具体查看 <自定义注解处理器类>)。查看注解处理器 module下的目录:\..\compiler\build\classes\java\main\META-INF\services,生成了注解处理器的清单文件,并将ARouterProcessor进行了注册。类似于将MainActivityAndroidManifest.xml文件中注册一样。
    在这里插入图片描述
    查看下javax.annotation.processing.Processor文件:

    com.purang.compiler.ARouterProcessor
    

二、javapoetsquare公司出品。

1. For Example

  • 1.1 其他配置与在上文中APT的配置一模一样,此外 在注解处理器module中添加依赖:

    dependencies {
        // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
        compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
        annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
        
        implementation 'com.squareup:javapoet:1.11.1'
    }
    
  • 1.2 同官方demo一样,先写一个Hello World的java类,按以下代码为模板:

    package com.example.helloworld;
    
    public final class HelloWorld {
      public static void main(String[] args) {
        System.out.println("Hello, JavaPoet!");
      }
    }
    
  • 1.3 编写ARouterProcessor2文件:

    @AutoService(Processor.class)
    @SupportedAnnotationTypes(value = {"com.purang.annotation.ARouter"})
    @SupportedSourceVersion(value = SourceVersion.RELEASE_7)
    @SupportedOptions(value = {"content"})
    public class ARouterProcessor2 extends AbstractProcessor {
    
        private Elements mElementsUtils;
        private Types mTypesUtils;
        private Messager mMessager;
        private Filer mFiler;
    
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            mElementsUtils = processingEnvironment.getElementUtils();
            mTypesUtils = processingEnvironment.getTypeUtils();
            mMessager = processingEnvironment.getMessager();
            String content = processingEnvironment.getOptions().get("content");
            mMessager.printMessage(Diagnostic.Kind.NOTE, "传递的参数:" + content);
            mFiler = processingEnvironment.getFiler();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
            if (set.isEmpty()) return false;
    
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            for (Element element : elements) {	
                MethodSpec methodSpec = MethodSpec.methodBuilder("main")
                        .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC)
                        .addParameter(String[].class, "args")
                        .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                        .build();
                TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld")
                        .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.FINAL)
                        .addMethod(methodSpec)
                        .build();
                JavaFile javaFile = JavaFile.builder("com.purang.compiler", typeSpec)
                        .build();
    
                try {
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    }
    

    依次执行Clean Project 、 Make Project命令,查看build目录:
    在这里插入图片描述
    可以看到生成了HelloWorld.java 和 .class文件。

  • 1.4 编写需要的ARouterProcessor2文件

    @AutoService(Processor.class)
    @SupportedAnnotationTypes(value = {"com.purang.annotation.ARouter"})
    @SupportedSourceVersion(value = SourceVersion.RELEASE_7)
    @SupportedOptions(value = {"content"})
    public class ARouterProcessor2 extends AbstractProcessor {
    
        private Elements mElementsUtils;
        private Types mTypesUtils;
        private Messager mMessager;
        private Filer mFiler;
    
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            mElementsUtils = processingEnvironment.getElementUtils();
            mTypesUtils = processingEnvironment.getTypeUtils();
            mMessager = processingEnvironment.getMessager();
            String content = processingEnvironment.getOptions().get("content");
            mMessager.printMessage(Diagnostic.Kind.NOTE, "传递的参数:" + content);
            mFiler = processingEnvironment.getFiler();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
            if (set.isEmpty()) return false;
    
            // 获取所有带ARouter注解的 类节点
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            // 遍历所有类节点
            for (Element element : elements) {
                // 通过类节点获取包节点(全路径:com.netease.xxx)
                String packageName = mElementsUtils.getPackageOf(element).getQualifiedName().toString();
                // 获取简单类名
                String className = element.getSimpleName().toString();
                mMessager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
                // 最终想生成的类文件名
                String finalClassName = className + "$$ARouter";
    
                // 高级写法,javapoet构建工具,参考(https://github.com/JakeWharton/butterknife)
                try {
                    // 获取类之上@ARouter注解的path值
                    ARouter aRouter = element.getAnnotation(ARouter.class);
    
                    // 构建方法体
                    MethodSpec method = MethodSpec.methodBuilder("findTargetClass") // 方法名
                            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                            .returns(Class.class) // 返回值Class<?>
                            .addParameter(String.class, "path") // 参数(String path)
                            // 方法内容拼接:
                            // return path.equals("/app/MainActivity") ? MainActivity.class : null
                            .addStatement("return path.equals($S) ? $T.class : null",
                                    aRouter.path(), ClassName.get((TypeElement) element))
                            .build(); // 构建
    
                    // 构建类
                    TypeSpec type = TypeSpec.classBuilder(finalClassName)
                            .addModifiers(Modifier.PUBLIC) //, Modifier.FINAL)
                            .addMethod(method) // 添加方法体
                            .build(); // 构建
    
                    // 在指定的包名下,生成Java类文件
                    JavaFile javaFile = JavaFile.builder(packageName, type)
                            .build();
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
    }
    

    依次执行Clean Project 、 Make Project命令,查看build目录:
    在这里插入图片描述

  • 1.5 使用到JavaPoet库的工程有:Retrofit ButterKnife

测试代码JiaGou_APT_JavaPoet

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值