注解-APT入门

注解是什么

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


annotation.png

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

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

@Retention


  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


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> classMap = new HashMap<>();
    // 存放Class对应的TypeElement
    Map 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 set, RoundEnvironment roundEnvironment) {//这里开始处理我们的注解解析了,以及生成Java文件
        collectInfo(roundEnvironment);
        writeToFile();
        return true;
    }

    private void collectInfo(RoundEnvironment roundEnvironment) {
        classMap.clear();
        classTypeElement.clear();
        //获取注解数量
        Set 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 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 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");

    }

调试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 ,随便命名,其他都用默认



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

你可能感兴趣的:(注解-APT入门)