美文网首页
架构师(五)——组件化APT(注解处理器)

架构师(五)——组件化APT(注解处理器)

作者: 王志强_9380 | 来源:发表于2020-12-01 19:41 被阅读0次
    image.png

    先在项目中创建一个java Library,命名为annotation
    创建注解文件ARouter.java

    
    /**
     * <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>
     *
     * 生命周期:SOURCE < CLASS < RUNTIME
     * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
     * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
     * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
     */
    @Target(ElementType.TYPE) // 该注解作用在类之上
    @Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作。注解会在class文件中存在
    public @interface ARouter {
    
        // 详细路由路径(必填),如:"/app/MainActivity"
        String path();
    
        // 路由组名(选填,如果开发者不填写,可以从path中截取出来)
        String group() default "";
    }
    

    在app的build.gradle中添加依赖

    // 依赖注解
    implementation project(':annotation')
    

    在app的activity中加入注解

    @ARouter(path = "/app/MainActivity")
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 不需要了
            // RecordPathManager.joinGroup("app", "MainActivity", MainActivity.class);
        }
    
        public void jump(View view) {
            Intent intent = new Intent(this, OrderActivity$$ARouter.findTargetClass("/app/OrderActivity"));
            startActivity(intent);
        }
    }
    

    接下来创建注解处理器文件
    先在根目录的build.gradle中引入第三方库

    buildscript {
        repositories {
            // 超级实用:某某影响,很多被墙,强烈推荐阿里云镜像更新
            maven {
                url "http://maven.aliyun.com/nexus/content/groups/public/"
            }
            google()
            jcenter()
            
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.4.1'
            
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            // 超级实用:某某影响,很多被墙,强烈推荐阿里云镜像更新
            maven {
                url "http://maven.aliyun.com/nexus/content/groups/public/"
            }
            google()
            jcenter()
            
        }
    }
    

    重新创建一个java Library,命名为compiler
    在bulid.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'
    
        // 引入annotation,让注解处理器-处理注解
        implementation project(':annotation')
    }
    
    // java控制台输出中文乱码
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
    
    // jdk编译版本1.7
    sourceCompatibility = "7"
    targetCompatibility = "7"
    

    创建注解处理器ARouterProcessor,继承AbstractProcessor(javax.annotation.processing.AbstractProcessor)

    /**
     * 编码此类1句话:细心再细心,出了问题debug真的不好调试
     */
    // AutoService则是固定的写法,加个注解即可
    // 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
    // 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
    @AutoService(Processor.class)
    // 允许/支持的注解类型,让注解处理器处理(新增annotation module)
    @SupportedAnnotationTypes({"com.netease.annotation.ARouter"})
    // 指定JDK编译版本
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    // 注解处理器接收的参数
    @SupportedOptions("content")
    public class ARouterProcessor extends AbstractProcessor {
    
        // 操作Element工具类 (类、函数、属性都是Element)
        private Elements elementUtils;
    
        // type(类信息)工具类,包含用于操作TypeMirror的工具方法
        private Types typeUtils;
    
        // Messager用来报告错误,警告和其他提示信息
        private Messager messager;
    
        // 文件生成器 类/资源,Filter用来创建新的源文件,class文件以及辅助文件
        private Filer filer;
    
        // 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            // 父类受保护属性,可以直接拿来使用。
            // 其实就是init方法的参数ProcessingEnvironment
            // processingEnv.getMessager(); //参考源码64行
            elementUtils = processingEnvironment.getElementUtils();
            messager = processingEnvironment.getMessager();
            filer = processingEnvironment.getFiler();
    
            // 通过ProcessingEnvironment去获取build.gradle传过来的参数
            String content = processingEnvironment.getOptions().get("content");
            // 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e那么好使
            messager.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 = elementUtils.getPackageOf(element).getQualifiedName().toString();
                // 获取简单类名
                String className = element.getSimpleName().toString();
                messager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
                // 最终想生成的类文件名
                String finalClassName = className + "$$ARouter";
    
                // 公开课写法,也是EventBus写法(https://github.com/greenrobot/EventBus)
                try {
                    // 创建一个新的源文件(Class),并返回一个对象以允许写入它
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
                    // 定义Writer对象,开启写入
                    Writer writer = sourceFile.openWriter();
                    // 设置包名
                    writer.write("package " + packageName + ";\n");
    
                    writer.write("public class " + finalClassName + " {\n");
    
                    writer.write("public static Class<?> findTargetClass(String path) {\n");
    
                    // 获取类之上@ARouter注解的path值
                    ARouter aRouter = element.getAnnotation(ARouter.class);
    
                    writer.write("if (path.equals(\"" + aRouter.path() + "\")) {\n");
    
                    writer.write("return " + className + ".class;\n}\n");
    
                    writer.write("return null;\n");
    
                    writer.write("}\n}");
    
                    // 最后结束别忘了
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            return true;
        }
    }
    

    上面的@SupportedOptions("content"),是app传递参数给注解处理器,可以在app的build.gradle中添加

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

    app中依赖注解处理器

    annotationProcessor project(':compiler')
    

    在写文件之前,可以先写一个模拟类,比如XActivity$$ARouter.java

    package com.netease.apt.test;
    
    import com.netease.apt.MainActivity;
    
    /**
     * 模拟APT生成后的文件样子
     */
    public class XActivity$$ARouter {
    
        public static Class<?> findTargetClass(String path) {
            if (path.equals("/app/MainActivity")) {
                return MainActivity.class;
            }
            return null;
        }
    }
    

    就是通过传进去path找到相应的class
    然后再到注解处理器里去写

    最后生成的文件在app\build\generated\source\apt\debug下,可以看到和我们之前测试写的一样

    javapoet写法
    javapoet写法是从内到外的,是面向对象的语法,先写方法实现,再写方法,再写类


    常用api 字符串格式化规则
    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 = elementUtils.getPackageOf(element).getQualifiedName().toString();
            // 获取简单类名
            String className = element.getSimpleName().toString();
            messager.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(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        return true;
    }
    

    相关文章

      网友评论

          本文标题:架构师(五)——组件化APT(注解处理器)

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