APT

APT,即注解处理器,是一种处理注解的工具。确切来说,它是javac的一个工具,用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,以生成.java文件作为输出。简单来说,就是在编译期通过注解生成.java文件。

Element

自定义注解处理器,需要继承AbstractProcessor类。对于AbstractProcessor来说,最重要的就是process方法,process方法处理的核心是Element对象。
下面我们详细看下Element对象。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package javax.lang.model.element;import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.type.TypeMirror;public interface Element extends AnnotatedConstruct {TypeMirror asType();//返回元素的类型,实际的对象类型。ElementKind getKind();//返回此元素的种类(即5种子类):包、类、接口、方法、字段等Set<Modifier> getModifiers();//返回此元素的修饰符,如public、static、final等关键字Name getSimpleName();//返回此元素的简单名称,如类名Element getEnclosingElement();//返回该元素的父元素;ExecutableElement的父级是TypeElemnt,而TypeElemnt的父级是PackageElment。//注意:包元素getEnclosingElement()返回是nullList<? extends Element> getEnclosedElements();//返回该元素所包含的元素,相当于在当前元素向里解一层。比如当前元素类型是class,getEnclosingElements()可以获取该类所包含的所有成员变量和方法。当前是包元素是,返回该包下的所有类元素。boolean equals(Object var1);int hashCode();List<? extends AnnotationMirror> getAnnotationMirrors();<A extends Annotation> A getAnnotation(Class<A> var1);//返回此元素针对指定类型的注解。注解可以是继承的,也可以是直接存在于此元素上的。<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}

除了上述的几个方法外,APT中还有以下几个方法比较常用

属性名 含义
getSimpleName() 获取名字,如果是类元素,不包含完整的包名路径
getQualifiedName() 获取全名,如果是类的话,包含完整的包名路径
getParameters() 获取方法的参数元素,每个元素是一个VariableElement
getReturnType() 获取方法元素的返回值
getConstantValue() 如果属性变量被final修饰,则可以使用该方法获取它的值

Element有5个直接子类,它们分别代表一种特定类型的元素。

类型 含义
TypeElement 一个类或接口程序元素
VariableElement 一个字段、enum常量、方法或构造方法参数、局部变量或异常参数
ExecutableElement 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
PackageElement 一个包程序元素
TypeParameterElement 一般类、接口、方法或构造方法元素的泛型参数

也许,这么说太过于官方了,我们举个简单的例子,大家一看就明白

package com.netease.apt; //PackageElement 包元素
public class Main{//TypeElement 类元素private int x: //VariableElement 属性元素private Main{}// ExecutableElement 方法元素
}

其中,TypeElement和VariableElement是最核心的两个Element。
接下来,我们通过APT来实现一个类似于ButterKnife中的@BindView注解。通过对View变量的注解,实现对View的绑定。

首先,我们新建一个java依赖库,专门来存放我们的自定义注解。


接下来,我们创建一个apt-processor模块(也是一个java模块),同时该模块要依赖于apt-annotation模块以及auto-service第三方库。

BrettBindViewProcesor继承AbstractProcessor类,并且需要在类上使用@AutoService(Processor.class)注解标记,表明当前类是一个注解处理器。
接着需要我们创建文件src/main/resources/META-INF/services/javax.annotation.processing.Processor进行声明。文件里的内容就是我们自定义的注解处理器的包名和类名。在本例中如下所示:

com.brett.apt_processor.BrettBindViewProcesor

AbstractProcessor类有几个重要的方法如下所示:

方法名 含义
init 初始化函数,可以得到ProcessingEnvironment对象。ProcessingEnvironment提供很多有用的工具类,如Elements、Types和Filer。
getSupportedAnnotationTypes 指定这个注解处理器是注册给哪个注解的,这里指定的是我们上面创建的注解@BrettBindView。
getSupportedSourceVersion 指定使用的Java版本,通常这里返回SourceVersion.latestSupported()。
package com.brett.apt_processor;import java.util.HashMap;
import java.util.Map;import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;/*** Created by Brett.li on 2022/7/3.*/
public class BrettClassCreateFactory {private String mBindingClassName;private String mPackageName;private TypeElement mTypeElement;private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();BrettClassCreateFactory(Elements elements, TypeElement classElement) {this.mTypeElement = classElement;PackageElement packageElement = elements.getPackageOf(mTypeElement);String packageName = packageElement.getQualifiedName().toString();String className = mTypeElement.getSimpleName().toString();this.mPackageName = packageName;this.mBindingClassName = className + "_BrettViewBinding";}public void putElement(int id, VariableElement element) {mVariableElementMap.put(id, element);}public String generateJavaCode() {StringBuilder builder = new StringBuilder();builder.append("package ").append(mPackageName).append(";\n");builder.append('\n');builder.append("public class ").append(mBindingClassName);builder.append(" {\n");generateBindViewMethods(builder);builder.append('\n');builder.append("}\n");return builder.toString();}private void generateBindViewMethods(StringBuilder builder) {builder.append("\tpublic void bindView(");builder.append(mTypeElement.getQualifiedName());builder.append(" owner ) {\n");for (int id : mVariableElementMap.keySet()) {VariableElement element = mVariableElementMap.get(id);String viewName = element.getSimpleName().toString();String viewType = element.asType().toString();builder.append("\t\towner.");builder.append(viewName);builder.append(" = ");builder.append("(");builder.append(viewType);builder.append(")(((android.app.Activity)owner).findViewById( ");builder.append(id);builder.append("));\n");}builder.append("  }\n");}public String getProxyClassFullName() {return mPackageName + "." + mBindingClassName;}public TypeElement getTypeElement(){return mTypeElement;}
}
package com.brett.apt_processor;import com.brett.apt_annotation.BrettBindView;
import com.google.auto.service.AutoService;import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;import javax.annotation.processing.AbstractProcessor;
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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.JavaFileObject;@AutoService(Processor.class)
//@SupportedSourceVersion(SourceVersion.RELEASE_8) //若使用了该注解,则不用复写getSupportedSourceVersion方法
//@SupportedAnnotationTypes({"com.brett.apt_annotation.BrettBindView"})  //若使用了该注解,则不用复写getSupportedAnnotationTypes方法
public class BrettBindViewProcesor extends AbstractProcessor {private Elements mElementUtils;private Map<String, BrettClassCreateFactory> mClassCreateFactoryMap = new HashMap<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mElementUtils = processingEnv.getElementUtils();}@Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> supportTypes = new LinkedHashSet<>();supportTypes.add(BrettBindView.class.getCanonicalName());return supportTypes;}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {mClassCreateFactoryMap.clear();//getElementsAnnotatedWith可以搜索到整个工程中所有使用了 BrettBindView 注解的元素Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BrettBindView.class);for (Element element : elements) {VariableElement variableElement = (VariableElement) element;//因为BrettBindView是作用在字段中,故可以强转为VariableElementTypeElement classElement = (TypeElement) variableElement.getEnclosingElement();//拿到父element,VariableElement的父element为TypeElementString fullClassName = classElement.getQualifiedName().toString();BrettClassCreateFactory proxy = mClassCreateFactoryMap.get(fullClassName);if (proxy == null) {proxy = new BrettClassCreateFactory(mElementUtils, classElement);mClassCreateFactoryMap.put(fullClassName, proxy);}BrettBindView bindAnnotation = variableElement.getAnnotation(BrettBindView.class);//拿到被BrettBindView注解注释的控件int id = bindAnnotation.value();proxy.putElement(id, variableElement);}//创建java文件for (String key : mClassCreateFactoryMap.keySet()) {BrettClassCreateFactory proxyInfo = mClassCreateFactoryMap.get(key);try {JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());Writer writer = jfo.openWriter();writer.write(proxyInfo.generateJavaCode());writer.flush();writer.close();} catch (IOException e) {e.printStackTrace();}}return true;//return 如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们}
}
//apt-processor模块的gradle文件
plugins {id 'java-library'
}java {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8
}dependencies {implementation 'com.google.auto.service:auto-service:1.0.1'implementation project(':apt-annotation')
}

最后,我们在app模块中引入上面两个模块即可。

dependencies {implementation 'androidx.appcompat:appcompat:1.4.2'implementation 'com.google.android.material:material:1.6.1'implementation 'androidx.constraintlayout:constraintlayout:2.1.4'implementation project(':apt-annotation')annotationProcessor project(':apt-processor')
}
package com.brett.myapplication;import androidx.appcompat.app.AppCompatActivity;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;import com.brett.apt_annotation.BrettBindView;
import com.brett.test1.Test1Activity;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class MainActivity extends AppCompatActivity {@BrettBindView(R.id.btn_main)Button btnMain;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
//        btnMain = findViewById(R.id.btn_main);bindView(this);btnMain.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Intent intent = new Intent(MainActivity.this, Test1Activity.class);startActivity(intent);}});}public static void bindView(Activity activity){Class clazz = activity.getClass();try {Class<?> bindViewClass = Class.forName(clazz.getName()+"_BrettViewBinding");Method method = bindViewClass.getMethod("bindView",activity.getClass());method.invoke(bindViewClass.newInstance(),activity);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}
}

注意:编译时可能会报异常,可能是autoService跟java版本不匹配导致的,需要调试一下。
当我们build之后,如果在build文件夹看到如下文件说明创建成功

Javapoet

在上面的例子中,我们通过StringBuilder拼接生成了对应的java代码。但是,这种做法比较繁琐、容易出错,而且难以维护。所以,我们可以使用javapoet库来生成Java代码,javapoet引入了oop的思想。
注意:使用javapoet生成代码未必就比使用原生方式好,如果复杂的代码生成,反而效率会低下。
javapoet是一个开源项目,GitHub地址:https://github.com/square/javapoet
我们在BrettClassCreateFactory类中添加如下代码:

//使用javapoet创建java代码public TypeSpec generateJavaCodeWithJavapoet(){return TypeSpec.classBuilder(mBindingClassName).addModifiers(Modifier.PUBLIC).addMethod(generateMethodsWithJavapoet()).build();}private MethodSpec generateMethodsWithJavapoet(){ClassName owner = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bindView").addModifiers(Modifier.PUBLIC).returns(void.class).addParameter(owner,"owner");for (int id : mVariableElementMap.keySet()) {VariableElement element = mVariableElementMap.get(id);String viewName = element.getSimpleName().toString();String viewType = element.asType().toString();methodBuilder.addCode("owner." + viewName + " = " + "(" + viewType + ")(((android.app.Activity)owner).findViewById( " + id + "));");}return methodBuilder.build();}public String getPackageName() {return mPackageName;}
package com.brett.apt_processor;import com.brett.apt_annotation.BrettBindView;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;import javax.annotation.processing.AbstractProcessor;
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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.JavaFileObject;@AutoService(Processor.class)
//@SupportedSourceVersion(SourceVersion.RELEASE_8) //若使用了该注解,则可以不用复写getSupportedSourceVersion方法
//@SupportedAnnotationTypes({"com.brett.apt_annotation.BrettBindView"})  //若使用了该注解,则可以不用复写getSupportedAnnotationTypes方法
public class BrettBindViewProcesor extends AbstractProcessor {private Elements mElementUtils;private Map<String, BrettClassCreateFactory> mClassCreateFactoryMap = new HashMap<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mElementUtils = processingEnv.getElementUtils();}@Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> supportTypes = new LinkedHashSet<>();supportTypes.add(BrettBindView.class.getCanonicalName());return supportTypes;}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {//。。。。。//创建java文件for (String key : mClassCreateFactoryMap.keySet()) {BrettClassCreateFactory proxyInfo = mClassCreateFactoryMap.get(key);//使用原始方式
//            try {//                JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
//                Writer writer = jfo.openWriter();
//                writer.write(proxyInfo.generateJavaCode());
//                writer.flush();
//                writer.close();
//            } catch (IOException e) {//                e.printStackTrace();
//            }//使用javapoet方式JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCodeWithJavapoet()).build();try{javaFile.writeTo(processingEnv.getFiler());}catch (Exception e){e.printStackTrace();}}return true;//返回true或false好像没有太大的区别}
}

JavaPoet相关

类对象 说明
MethodSpec 代表一个构造函数或方法声明
TypeSpec 代表一个类,接口或枚举声明
FieldSpec 代表一个成员变量、字段声明
JavaFile 代表一个顶级类的java文件
Parameterspec 用来创建参数
AnnotationSpec 用来创建注解
ClassName 用来包装一个类
TypeName 类型,如在添加返回值类型是使用 TypeName . VoID

最后,我们build下工程同样可以生成MainActivity_BrettViewBinding文件。
注意:如果在不同的模块中都引入了apt模块,有可能会生成多份同名的文件,必须要对生成的文件进行标识。

APT和Javapoet的精彩联动相关推荐

  1. 注解与APT、JavaPoet

    Android APT 注解处理工具(APT) +注解处理(AbstractProcess)+代码处理(JavaPoet)+处理器注册(AutoService) javapoet官方 原文:https ...

  2. Dagger2的使用以及原理分析

    使用 Dagger2的使用说起来并不难,关键在于要掌握Dagger2的提供的几个注解及其意思. 环境搭建 在模块级的build.gradle文件中加入如下依赖: plugins {id 'com.an ...

  3. 万能的APT!编译时注解的妙用

    转载自:http://zjutkz.net/2016/04/07/万能的APT!编译时注解的妙用/ 本篇文章会带你了解什么是注解,注解的用法和分类,并且从knight和butterKnife的使用方式 ...

  4. 【建议收藏】2020年中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂(Android基础篇)...

    前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~. A awesome android expert interview questions a ...

  5. Android基础知识笔记

    ## Android基础面试题 (⭐⭐⭐) #### 1.什么是ANR 如何避免它? 答:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应 用程 ...

  6. android布局优化方案,Android启动优化-布局优化

    Android启动优化-布局优化 安卓应用开发发展到今天,已经成为一个非常成熟的技术方向,从目前的情况看,安卓开发还是一个热火朝天的发展,但高级人才却相对较少,如今移动互联网的开发者也逐渐开始注重插入 ...

  7. 技术分享 | 混合云模式下SaaS端前端最佳实践

    导读:集成开放平台采用的是混合云部署架构,包含两个大的组件,管理控制台和引擎.管理控制台是SaaS的,部署在公有云,按租户隔离.引擎部署在客户私有云.一套SaaS版的管理控制台如何适配不同客户的引擎, ...

  8. 技术分享 | CodeReview主要Review什么?

    源宝导读:Code Review, 意即代码审查,是指一种有意识和系统的召集其他程序员来检查彼此的代码是否有错误的地方. 在敏捷团队中推行CodeReview, 可以帮助团队快速成长.本文将分享在&q ...

  9. 技术分享 | 【构建服务端SDK】之连接中心统一调用SDK

    源宝导读:微服务架构与传统的单体式方案的最大不同是微服务将应用的核心功能拆分成多项服务.每项服务可以单独构建和部署.服务之间需要互相通信.假设服务间每次通信都需要在调用方编码操作,那么必定会增加很大的 ...

最新文章

  1. 松江库卡机器人_上海高质量发展调研行|库卡机器人:为全球提供“松江创造”的产品...
  2. 如何用XGBoost做时间序列预测?
  3. [单刷APUE系列]第八章——进程控制[1]
  4. 一个锁等待现象的诊断案例
  5. Golang Study 一 定时器使用
  6. 要显示的8个字符已存放在以BUF开始的存储区单元中(称为显示缓冲区),依次送到LED显示器中显示。CPU通过P0口和P2口控制8位LED显示器,LED为共阴极显示器。
  7. 回弹强度记录表填写_回弹法检测砼抗压强度原始记录表(2011年规程)
  8. 铺瓷砖问题的C++实现
  9. 电脑win7系统开机密码忘记
  10. 从零开始用uniapp搭建一个APP
  11. 有一种爱情叫做冯小刚与徐帆
  12. best-time-to-buy-and-sell-stock
  13. vue-awesome-swiper 设置autoHeight,最后不满一页也占了一页
  14. VMware Workstation虚拟机网络相关配置
  15. 强一致性、弱一致性、顺序一致性、最终一致性概述
  16. 编写一个程序,计算1000以内不能被7整除的数值和
  17. 从程序员到测试工程师
  18. Directshow学习笔记六-----重新压缩一个AVI文件(个人学习总结,仅供参考)
  19. mysql修改用户密码的方法及命令
  20. ① ESP8266 开发学习笔记_By_GYC 【更新 ets_printf 函数 使ESP_IDF 能够支持浮点数打印】

热门文章

  1. 【OPC UA】C# 通过OpcUaHelper建立OPC客户端访问KEPServerEx6 OPC服务器数据
  2. InterConnect 和SmartConnect
  3. 普拉图和施泰纳问题的实验解法
  4. 「数据游戏」:使用 ARIMA 算法预测三日后招商银行收盘价
  5. 如何评价「仙剑奇侠传六」使用Unity 3D引擎?
  6. 什么人才适合学习嵌入式?嵌入式就业做什么?
  7. Android SDK 开发流程
  8. 【密码学】 一篇文章讲透数字证书
  9. 下载spotify音乐_如何将Google Maps音乐控件用于Spotify,Apple Music或Google Play音乐
  10. 基于PT8.2柔性传感器使用