美文网首页
注解-APT入门

注解-APT入门

作者: XII01 | 来源:发表于2020-04-19 16:02 被阅读0次

    注解是什么

    注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释代码的一部分。注解对于代码的运行效果没有直接影响。


    annotation.png

    @Retention, @Target, @Inherited, @Documented,它们是用来定义 Annotation 的 Annotation。也就是当我们要自定义注解时,需要使用它们。

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    public @interface BindView {
        int value();
    }
    

    @Retention


    WX20200419-144524@2x.png
    1. @Retention(RetentionPolicy.SOURCE)
    • 源码时注解,一般用来作为编译器标记。
    • 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;源码注解(RetentionPolicy.SOURCE)的生命周期只存在Java源文件这一阶段,是3种生命周期中最短的注解。当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时会把Java源程序上的源码注解给去掉。需要注意的是,在编译器处理期间源码注解还存在,即注解处理器Processor 也能处理源码注解,编译器处理完之后就没有该注解信息了。
    1. @Retention(RetentionPolicy.RUNTIME)
    • 运行时注解,在运行时通过反射去识别的注解@Retention(RetentionPolicy.RUNTIME)即可。
    • 运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简答
    1. @Retention(RetentionPolicy.CLASS)
    • 编译时注解,在编译时被识别并处理的注解。
    • 编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。
    //用来表示这个注解可以使用在哪些地方。比如:类、方法、属性、接口等等。
    // 这里ElementType.TYPE 表示这个注解可以用来修饰:Class, interface or enum declaration。
    // 当你用ContentView修饰一个方法时,编译器会提示错误。
    @Target(ElementType.TYPE)
    //用来修饰这是一个什么类型的注解。这里表示该注解是一个运行时注解。这样APT就知道啥时候处理这个注解了。
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ContentView {
        //返回值表示这个注解里可以存放什么类型值
        int value();
    }
    

    什么是APT

    AP: 就是Annotation Processing Tool的简称,叫做注解处理工具。就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。
    原理: Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解解析逻辑。APT 的原理就是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码.

    我们来实现butterknife绑定view功能
    第一步:定义注解 @BindView

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    public @interface BindView {
        int value();
    }
    

    第二步:实现 AbstractProcessor

    1. 新建一个Java lib


      WX20200419-152613@2x.png
    package com.xxw.compiler;
    
    import com.google.auto.service.AutoService;
    import com.squareup.javapoet.JavaFile;
    import com.squareup.javapoet.MethodSpec;
    import com.squareup.javapoet.ParameterSpec;
    import com.squareup.javapoet.TypeName;
    import com.squareup.javapoet.TypeSpec;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Filer;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.Processor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.annotation.processing.SupportedSourceVersion;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.Modifier;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.element.VariableElement;
    import javax.lang.model.util.Elements;
    
    import static javax.lang.model.element.Modifier.*;
    
    @AutoService(Processor.class)
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    @SupportedAnnotationTypes({"com.xxw.compiler.BindView"})
    public class MyProcessor extends AbstractProcessor {
        // 存放同一个Class下的所有注解
        Map<String, List<VariableInfo>> classMap = new HashMap<>();
        // 存放Class对应的TypeElement
        Map<String, TypeElement> classTypeElement = new HashMap<>();
        private Filer filer;
        Elements elementUtils;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
    
            //初始化我们需要的基础工具
            filer = processingEnv.getFiler();
            elementUtils = processingEnv.getElementUtils();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {//这里开始处理我们的注解解析了,以及生成Java文件
            collectInfo(roundEnvironment);
            writeToFile();
            return true;
        }
    
        private void collectInfo(RoundEnvironment roundEnvironment) {
            classMap.clear();
            classTypeElement.clear();
            //获取注解数量
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
            for (Element element : elements) {
                // 获取 BindView 注解的值
                int viewId = element.getAnnotation(BindView.class).value();
    
                // 代表被注解的元素
                VariableElement variableElement = (VariableElement) element;
    
                //被注解元素所在的Class
                TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
                // Class的完整路径
                String classFullName = typeElement.getQualifiedName().toString();
    
                // 收集Class中所有被注解的元素
                List<VariableInfo> variableList = classMap.get(classFullName);
                if (variableList == null) {
                    variableList = new ArrayList<>();
                    classMap.put(classFullName, variableList);
    
                    // 保存Class对应要素(名称、完整路径等)
                    classTypeElement.put(classFullName, typeElement);
                }
                VariableInfo variableInfo = new VariableInfo();
                variableInfo.setVariableElement(variableElement);
                variableInfo.setViewId(viewId);
                variableList.add(variableInfo);
            }
        }
    
    
        private void writeToFile() {
            try {
                for (String classFullName : classMap.keySet()) {
                    TypeElement typeElement = classTypeElement.get(classFullName);
    
                    // 使用构造函数绑定数据
                    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
                            .addModifiers(PUBLIC)
                            .addParameter(ParameterSpec.builder(TypeName.get(typeElement.asType()), "activity").build());
                    List<VariableInfo> variableList = classMap.get(classFullName);
                    for (VariableInfo variableInfo : variableList) {
                        VariableElement variableElement = variableInfo.getVariableElement();
                        // 变量名称(比如:TextView tv 的 tv)
                        String variableName = variableElement.getSimpleName().toString();
                        // 变量类型的完整类路径(比如:android.widget.TextView)
                        String variableFullName = variableElement.asType().toString();
                        // 在构造方法中增加赋值语句,例如:activity.tv = (android.widget.TextView)activity.findViewById(215334);
                        constructor.addStatement("activity.$L=($L)activity.findViewById($L)", variableName, variableFullName, variableInfo.getViewId());
                    }
    
                    // 构建Class
                    TypeSpec typeSpec = TypeSpec.classBuilder(typeElement.getSimpleName() + "$$ViewInjector")
                            .addModifiers(PUBLIC)
                            .addMethod(constructor.build())
                            .build();
    
                    // 与目标Class放在同一个包下,解决Class属性的可访问性
                    String packageFullName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
                    JavaFile javaFile = JavaFile.builder(packageFullName, typeSpec)
                            .build();
                    // 生成class文件
                    javaFile.writeTo(filer);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    }
    

    然后在app 工程下引入

     @BindView(R.id.tv)
        TextView mView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            InjectHelper.inject(this);
    
            mView.setText(" tv is injected");
    
        }
    
    WX20200419-154608@2x.png

    调试apt代码
    第一步在init打断点
    第二步在自己的.gradle 目录下gradle.properties,没有就自己建一个,加上下面代码

    org.gradle.daemon=true
    org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
    

    1、找到:Select Run/Debug Configration -> Edit Configrations...,
    2、在面板上添加Remote ,随便命名,其他都用默认


    WX20200419-155156@2x.png

    然后调试你的代码吧,如果端口被占用,先关掉。
    可以试试自己实现onclick

    相关文章

      网友评论

          本文标题:注解-APT入门

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