APT

APT-概念了解

友情链接:

https://lizhaoxuan.github.io/2016/07/17/apt-Grammar-explanation/

https://github.com/Gavin-ZYX/APTTest.git

apt

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注释自动生成代码。Annotation处理器在出来Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

annotationProcessor

annotationProcessor是APT工具中的一种,他是google开发的内置框架,不需要引入,可以直接在build.gradle文件中使用

android-apt

android-apt是由一位开发者自己开发的apt框架,源代码托管在这里,随着Android Gradle 插件 2.2 版本的发布,Android Gradle 插件提供了名为 annotationProcessor 的功能来完全代替 android-apt ,自此android-apt 作者在官网发表声明最新的Android Gradle插件现在已经支持annotationProcessor,并警告和或阻止android-apt ,并推荐大家使用 Android 官方插件annotationProcessor。

Demo-知识点

注解

@Retention

  • @Retention(RetentionPolicy.SOURCE) 源码时注解,一般用来作为编译器标记。就比如Override, Deprecated, SuppressWarnings这样的注解
  • @Retention(RetentionPolicy.RUNTIME) 运行时注解,一般在运行时通过反射去识别的注解
  • @Retention(RetentionPolicy.CLASS) 编译时注解,在编译时处理

@Target

  • @Target(ElementType.TYPE) 接口、类、枚举、注解
  • @Target(ElementType.FIELD)字段、枚举的常量
  • @Target(ElementType.METHOD) 方法
  • @Target(ElementType.PARAMETER) 方法参数
  • @Target(ElementType.CONSTRUCTOR) 构造函数
  • @Target(ElementType.LOCAL_VARIABLE) 局部变量
  • @Target(ElementType.ANNOTATION_TYPE) 注解
  • @Target(ElementType.package) 包

@Inherited

该注解的字面意识是继承,但你要知道注解是不可以继承的。
ie:当你的注解定义到类A上,此时,有个B类继承A,且没使用该注解。但是扫描的时候,会把A类设置的注解,扫描到B类上

输出Log

Messager

//取得Messager对象
Messager messager = processingEnv.getMessager();

Processor日志输出的位置在编译器下方的Messages窗口中

Processor支持最基础的System.out方法

同样Processor也有自己的Log输出工具: Messager

同Log类似,Messager也有日志级别的选择

  • Diagnostic.Kind.ERROR
  • Diagnostic.Kind.WARNING
  • Diagnostic.Kind.MANDATORY_WARNING
  • Diagnostic.Kind.NOTE
  • Diagnostic.Kind.OTHER

Element

Represents a program element such as a package, class, or method.
Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine).
表示一个程序元素,比如包、类或者方法

ExecutableElement

表示某个类或接口的方法构造方法初始化程序(静态或实例),包括注释类型元素。

对应@Target(ElementType.METHOD) @Target(ElementType.CONSTRUCTOR)

PackageElement;

表示一个包程序元素。提供对有关包极其成员的信息访问。

对应@Target(ElementType.PACKAGE)

TypeElement;

表示一个接口程序元素。提供对有关类型极其成员的信息访问。

对应@Target(ElementType.TYPE)

注意:枚举类型是一种类,而注解类型是一种接口。

TypeParameterElement;

表示一般类、接口、方法或构造方法元素的类型参数

对应@Target(ElementType.PARAMETER)

VariableElement;

表示一个字段enum常量、方法或构造方法参数局部变量异常参数

对应@Target(ElementType.LOCAL_VARIABLE)

修饰方法的注解和ExecutableElement

当你有一个注解是以@Target(ElementType.METHOD)定义时,表示该注解只能修饰方法。

那么这个时候你为了生成代码,而需要获取一些基本信息:包名、类名、方法名、参数类型、返回值。

//OnceClick.class 以 @Target(ElementType.METHOD)修饰
for (Element element : roundEnv.getElementsAnnotatedWith(OnceClick.class)) {
    //对于Element直接强转
    ExecutableElement executableElement = (ExecutableElement) element;

    //非对应的Element,通过getEnclosingElement转换获取
    TypeElement classElement = (TypeElement) element
                .getEnclosingElement();

    //当(ExecutableElement) element成立时,使用(PackageElement) element
    //            .getEnclosingElement();将报错。
    //需要使用elementUtils来获取
    Elements elementUtils = processingEnv.getElementUtils();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);

    //全类名
    String fullClassName = classElement.getQualifiedName().toString();
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //方法名
    String methodName = executableElement.getSimpleName().toString();

    //取得方法参数列表
    List methodParameters = executableElement.getParameters();
    //参数类型列表
    List types = new ArrayList<>();
    for (VariableElement variableElement : methodParameters) {
        TypeMirror methodParameterType = variableElement.asType();
        if (methodParameterType instanceof TypeVariable) {
            TypeVariable typeVariable = (TypeVariable) methodParameterType;
            methodParameterType = typeVariable.getUpperBound();

        }
        //参数名
        String parameterName = variableElement.getSimpleName().toString();
        //参数类型
        String parameteKind = methodParameterType.toString();
        types.add(methodParameterType.toString());
    }
}

修饰属性、类成员的注解和VariableElement

当你有一个注解是以@Target(ElementType.FIELD)定义时,表示该注解只能修饰属性、类成员。

那么这个时候你为了生成代码,而需要获取一些基本信息:包名、类名、类成员类型、类成员名

如何获取:

for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
    //ElementType.FIELD注解可以直接强转VariableElement
    VariableElement variableElement = (VariableElement) element;

    TypeElement classElement = (TypeElement) element
            .getEnclosingElement();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //类成员名
    String variableName = variableElement.getSimpleName().toString();

    //类成员类型
    TypeMirror typeMirror = variableElement.asType();
    String type = typeMirror.toString();

}

修饰类的注解和TypeElement

当你有一个注解是以@Target(ElementType.TYPE)定义时,表示该注解只能修饰类、接口、枚举。

那么这个时候你为了生成代码,而需要获取一些基本信息:包名、类名、全类名、父类。

如何获取:

for (Element element : roundEnv.getElementsAnnotatedWith(xxx.class)) {
    //ElementType.TYPE注解可以直接强转TypeElement
    TypeElement classElement = (TypeElement) element;

    PackageElement packageElement = (PackageElement) element
                .getEnclosingElement();

    //全类名
    String fullClassName = classElement.getQualifiedName().toString();
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
     //父类名
     String superClassName = classElement.getSuperclass().toString();

}

Demo

module

apt-annotation (java-library)
apt-processor  (java-library)
apt-library    (com.android.library)

apt-annotation

注解类BindView(编译时注解)

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

build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

apt-processor

build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation 'com.squareup:javapoet:1.10.0'
    implementation project(':apt-annotation')
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

AbstractProcessor

@SupportedAnnotationTypes({"com.example.gavin.apt_annotation.BindView"})
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
  @Override
  public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
  }

  @Override
  public Set getSupportedAnnotationTypes() {
    return super.getSupportedAnnotationTypes();
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return super.getSupportedSourceVersion();
  }

  @Override
  public boolean process(Set set, RoundEnvironment roundEnvironment) {
    return false;
  }
}

添加自己需要处理的注解,可以通过两种方式:getSupportedAnnotationTypes()或者直接用注解
@SupportedAnnotationTypes("全路径")

process方法

mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
    // 1.会执行多次,所以要先clear
    mProxyMap.clear();
    // 2.得到所有的注解并收集到map中
    Set elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
    for (Element element : elements) {
      VariableElement variableElement = (VariableElement) element;
      TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
      String fullClassName = classElement.getQualifiedName().toString();

      //elements的信息保存到mProxyMap中
      ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
      if (proxy == null) {
        proxy = new ClassCreatorProxy(mElementUtils, classElement);
        mProxyMap.put(fullClassName, proxy);
      }
      BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
      int id = bindAnnotation.value();
      proxy.putElement(id, variableElement);
    }
    // 3.通过遍历mProxyMap,创建java文件 通过javapoet生成
    for (String key : mProxyMap.keySet()) {
      ClassCreatorProxy proxyInfo = mProxyMap.get(key);
      JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
      try {
        // 生成文件
        javaFile.writeTo(mFiler);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
    return true;

注:APT有一个局限性,就是只会扫描Java源码,不会扫描jar ,aar 和class 。也就是说,所有模块需要以源码形式存在。而现在通用的做法是,将模块打包成jar或者aar,发布到Maven库,再由其他模块自行引用,解决这个问题的基本方法用gradle plugin方法(项目中的medusa库就是插件的方式)

第三方库分析

Arouter

ButterKnife

EventBus

你可能感兴趣的:(APT)