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<? extends VariableElement> methodParameters = executableElement.getParameters();
    //参数类型列表
    List<String> 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<String> getSupportedAnnotationTypes() {
    return super.getSupportedAnnotationTypes();
  }

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

  @Override
  public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    return false;
  }
}

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

process方法

mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
    // 1.会执行多次,所以要先clear
    mProxyMap.clear();
    // 2.得到所有的注解并收集到map中
    Set<? extends Element> 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容