0
点赞
收藏
分享

微信扫一扫

APT 及实现ButterKnife


APT 及实现ButterKnife

  • ​​1. 译时和运行时注解​​
  • ​​2. 什么是 APT​​
  • ​​3. AbstractProcessor​​
  • ​​4. 结构体语言 -- Java​​
  • ​​4.1 Java -- Element 和 Elements​​
  • ​​5. 自定义编译注解处理器​​
  • ​​5.1 在 annotation module 中定义注解​​
  • ​​5.2 在 compiler module 中添加 注解处理器 Processor​​
  • ​​5.3 在 library module 中创建要生成的文件​​
  • ​​5.4 使用​​
  • ​​5.5 编译后生成的代码​​

了解 ​​APT​​​ 的相关知识,相信手动实践后你会更容易理解像 ​​Dagger​​​、​​ARouter​​​、​​ButterKnife​​ 等这种使用了编译时注解的框架,也更容易理解其内部源码实现,内容如下:

  • 编译时和运行时注解
  • 注解处理器APT
  • AbstractProcessor
  • Element和Elements
  • 自定义注解处理器
  • 使用自定义注解处理器
  • 编译时和运行时注解

1. 译时和运行时注解

先了解一下编译时和运行时的区别:

  1. 编译时:指编译器将源代码翻译成机器能够识别的代码的过程,Java 中也就是将 Java 源代码编译为 JVM 识别的字节码文件的过程。
  2. 运行时:指 JVM 分配内存、解释执行字节码文件的过程

元注解 ​​@Retention​​ 决定注解是在编译时还是在运行时,其可配置策略如下:

public enum RetentionPolicy {
SOURCE, //在编译时会被丢弃,仅仅在源码中存在
CLASS, //默认策略,运行时就会被丢弃,仅仅在 class 文件中
RUNTIME //编译时会将注解信息记录到class文件,运行时任然保留,可以通过反射获取注解信息
}

编译时注解和运行时注解除了上面的区别之外,实现方式也不一样:

  • 编译时注解一般是通过注解处理器(APT)来实现的
  • 运行时注解一般是通过反射来实现的

2. 什么是 APT

APT (Annotation Processing Tool)

是一种处理注释的工具,它对源代码文件进行检测并找出其中的 Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够运行,必须要通过 APT 工具来处理。

简单说:根据规则,帮我们生成代码、生成类文件

编译时注解就是通过 APT 来通过注解信息生成代码来完成某些功能,典型代表有 ​​ButterKnife​​​、​​Dagger​​​、​​ARouter​​ 等

3. AbstractProcessor

​AbstractProcessor​​​ 实现 ​​Processor​​​ 是注解处理器的抽象类,要实现注解处理器都需要继承 ​​AbstractProcessor​​ 进行扩展,主要方法说明如下:

  • init:初始化Processor,可从参数 ProcessingEnvironment 中获取工具类 Elements、Types、Filer 以及 Messager 等;
  • getSupportedSourceVersion:返回使用的 Java 版本;
  • getSupportedAnnotationTypes:返回要处理的的所有的注解名称;
  • process:获取所有指定的注解进行处理;
  • process 方法可能会在运行过程中执行多次,知道没有其他类产生为止。

4. 结构体语言 – Java

eg: html 就是一种 element 组成的结构体

APT 及实现ButterKnife_java

对于 Java 源文件来说,它同样也是一种结构体语言

// PackageElement
package manu.com.compiler;

// TypeElement
public class ElementSample {
// VariableElement
private int a;
// VariableElement
private Object other;

// ExecutableElement
public ElementSample() {
}
// 方法参数VariableElement
public void setA(int newA) {
}
// TypeParameterElement表示参数化类型,用在泛型参数中
}

4.1 Java – Element 和 Elements

​Element​​ 类似于 XML 中的标签一样,在 Java 中 Element 表示程序元素,如类、成员、方法等,每个 ​​Element​​​ 都表示仅表示某种结构,对 ​​Element​​​ 对象类的操作,请使用 ​​visitor​​​ 或 ​​getKind()​​ 方法进行判断再具体处理,Element 的子类如下:

  • ExecutableElement
  • PackageElement
  • Parameterizable
  • QualifiedNameable
  • TypeElement
  • TypeParameterElement
  • VariableElement

​Elements​​​ 是处理 ​​Element​​ 的工具类,只提供接口,具体有 Java 平台实现

APT 及实现ButterKnife_自定义_02


APT 及实现ButterKnife_自定义_03

5. 自定义编译注解处理器

下面使用 ​​APT​​​ 实现一个自定义的注解 ​​@BindView​​​ 来替换来模仿 ​​ButterKnife​​​ 的注解 ​​@BindView​​,案例项目结构如下:

APT 及实现ButterKnife_工具类_04

5.1 在 annotation module 中定义注解

// SOURCE 注解仅在源码中保留,class文件中不存在
// CLASS 注解在源码和class文件中都存在,但运行时不存在
// RUNTIME 注解在源码,class文件中存在且运行时可以通过反射机制获取到
@Target(ElementType.FIELD) // 注解作用在属性之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface BindView {

// 返回R.id.xx值
int value();
}

@Target(ElementType.METHOD) // 注解作用在方法之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface OnClick {

// 此处省略了int[]
int value();
}

5.2 在 compiler module 中添加 注解处理器 Processor

引入 Google 的 ​​auto-service​​​ 用来生成 ​​META-INFO​​​ 下的相关文件,​​javapoet​​​ 用于更方便的创建 ​​Java​​ 文件,自定义注解处理器 如下:

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

compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

// 帮助我们通过类调用的形式来生成Java代码
implementation "com.squareup:javapoet:1.10.0"
// 引入annotation,处理@BindView、@Onclick注解
implementation project(':annotation')
}

// 中文乱码问题(错误: 编码GBK的不可映射字符)
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}

// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.BINDVIEW_ANNOTATION_TYPES, Constants.ONCLICK_ANNOTATION_TYPES})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ButterKnifeProcessor extends AbstractProcessor {

// 操作Element工具类 (类、函数、属性都是Element)
private Elements elementUtils;

// type(类信息)工具类,包含用于操作TypeMirror的工具方法
private Types typeUtils;

// Messager用来报告错误,警告和其他提示信息
private Messager messager;

// 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件
private Filer filer;

// key:类节点, value:被@BindView注解的属性集合
private Map<TypeElement, List<VariableElement>> tempBindViewMap = new HashMap<>();

// key:类节点, value:被@OnClick注解的方法集合
private Map<TypeElement, List<ExecutableElement>> tempOnClickMap = new HashMap<>();

// 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// 初始化
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
messager.printMessage(Diagnostic.Kind.NOTE,
"注解处理器初始化完成,开始处理注解------------------------------->");
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
// 一旦属性上使用了@BindView注解
if (!EmptyUtils.isEmpty(set)) {
// 获取所有被 @BindView注解的 属性元素集合
Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);

// 获取所有被 @OnClick注解的 方法元素集合
Set<? extends Element> onClickElements = roundEnv.getElementsAnnotatedWith(OnClick.class);

if (!EmptyUtils.isEmpty(bindViewElements) || !EmptyUtils.isEmpty(onClickElements)) {
// 收集信息,存储到temp集合中。用来生成代码
valueOfMap(bindViewElements, onClickElements);

try {
// 生成类文件
createJavaFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
return false;
}

/**
* 收集信息,存储Map赋值
*/
private void valueOfMap(Set<? extends Element> bindViewElements, Set<? extends Element> onClickElements) {
if (!EmptyUtils.isEmpty(bindViewElements)) {
for (Element element : bindViewElements) {
messager.printMessage(Diagnostic.Kind.NOTE, "@BindView >> " + element.getSimpleName());
if (element.getKind() == ElementKind.FIELD) {
VariableElement fieldElement = (VariableElement) element;
// 属性节点,上一个(父节点),类节点
TypeElement typeElement = (TypeElement) fieldElement.getEnclosingElement();
// 如果map集合中包含了key(类节点)
if (tempBindViewMap.containsKey(typeElement)) {
tempBindViewMap.get(typeElement).add(fieldElement);
} else {
List<VariableElement> fields = new ArrayList<>();
fields.add(fieldElement);
tempBindViewMap.put(typeElement, fields);
}
}
}
}

if (!EmptyUtils.isEmpty(onClickElements)) {
for (Element element : onClickElements) {
messager.printMessage(Diagnostic.Kind.NOTE, "@OnClick >> " + element.getSimpleName());
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement methodElement = (ExecutableElement) element;
// 属性节点,上一个(父节点),类节点
TypeElement typeElement = (TypeElement) methodElement.getEnclosingElement();
// 如果map集合中包含了key(类节点)
if (tempOnClickMap.containsKey(typeElement)) {
tempOnClickMap.get(typeElement).add(methodElement);
} else {
List<ExecutableElement> methods = new ArrayList<>();
methods.add(methodElement);
tempOnClickMap.put(typeElement, methods);
}
}
}
}
}

private void createJavaFile() throws IOException {
// 判断是否有需要生成的类文件(有坑)
if (!EmptyUtils.isEmpty(tempBindViewMap)) {

// 获取接口的类型
TypeElement viewBinderType = elementUtils.getTypeElement(Constants.VIEWBINDER);
TypeElement clickListenerType = elementUtils.getTypeElement(Constants.CLICKLISTENER);
TypeElement viewType = elementUtils.getTypeElement(Constants.VIEW);

// 从下往上写(JavaPoet技巧)
for (Map.Entry<TypeElement, List<VariableElement>> entry : tempBindViewMap.entrySet()) {

// 类名(TypeElement)
ClassName className = ClassName.get(entry.getKey());
// 实现接口泛型(implements ViewBinder<MainActivity>)
ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBinderType),
ClassName.get(entry.getKey()));

// 方法参数体
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), // MainActivity
Constants.TARGET_PARAMETER_NAME) // 方法参数名target
.addModifiers(Modifier.FINAL) // 参数修饰符
.build(); // 参数体构建完成

// 方法体:public void bind(final MainActivity target) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME) // bind方法名
.addAnnotation(Override.class) // 接口重写方法
.addModifiers(Modifier.PUBLIC) // 方法修饰符
.addParameter(parameterSpec); // 方法参数

for (VariableElement fieldElement : entry.getValue()) {
// 获取属性名
String fieldName = fieldElement.getSimpleName().toString();
// 获取@BindView注解的值
int annotationValue = fieldElement.getAnnotation(BindView.class).value();
// target.tv = target.findViewById(R.id.tv);
String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
// 加入方法内容
methodBuilder.addStatement(methodContent,
Constants.TARGET_PARAMETER_NAME, // target
Constants.TARGET_PARAMETER_NAME, // target
annotationValue); // R.id.xx
}

if (!EmptyUtils.isEmpty(tempOnClickMap)) {
for (Map.Entry<TypeElement, List<ExecutableElement>> methodEntry : tempOnClickMap.entrySet()) {
// 类名
if (className.equals(ClassName.get(methodEntry.getKey()))) {
for (ExecutableElement methodElement : methodEntry.getValue()) {
// 获取方法名
String methodName = methodElement.getSimpleName().toString();
// 获取@OnClick注解的值
int annotationValue = methodElement.getAnnotation(OnClick.class).value();

/**
* target.findViewById(R.id.tv).setOnClickListener(new DebouncingOnClickListener() {
*
* @Override
* public void doClick(View v) {
* target.click(v);
* }
* });
*/
methodBuilder.beginControlFlow("$N.findViewById($L).setOnClickListener(new $T()",
Constants.TARGET_PARAMETER_NAME, annotationValue, ClassName.get(clickListenerType))
.beginControlFlow("public void doClick($T v)", ClassName.get(viewType))
.addStatement("$N." + methodName + "(v)", Constants.TARGET_PARAMETER_NAME)
.endControlFlow()
.endControlFlow(")")
.build();
}
}
}
}

// 生成必须是同包:(属性的修饰符是缺失的)
JavaFile.builder(className.packageName(), // 包名
TypeSpec.classBuilder(className.simpleName() + "$ViewBinder") // 类名
.addSuperinterface(typeName) // 实现ViewBinder接口(有泛型)
.addModifiers(Modifier.PUBLIC) // 类修饰符
.addMethod(methodBuilder.build()) // 加入方法体
.build()) // 类构建完成
.build() // JavaFile构建
.writeTo(filer); // 文件生成器开始生成类文件
}
}
}
}

5.3 在 library module 中创建要生成的文件

/**
* 核心类,接口 = 接口实现类
* ButterKnife用的是构造方法.newInstance()
* 接口.bind()
*/
public class ButterKnife {

public static void bind(Activity activity) {
// 拼接类名,如:MainActivity$ViewBinder
String className = activity.getClass().getName() + "$ViewBinder";

try {
// 加载上述拼接类
Class<?> viewBinderClass = Class.forName(className);
// 接口 = 接口实现类
ViewBinder viewBinder = (ViewBinder) viewBinderClass.newInstance();
// 调用接口方法
viewBinder.bind(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 点击监听接口,实现类(抽象类 + 抽象方法)
public abstract class DebouncingOnClickListener implements View.OnClickListener {

@Override
public void onClick(View v) {
// 调用抽象方法
doClick(v);
}

public abstract void doClick(View v);
}

/**
* 接口绑定类(所有注解处理器生的类,都需要实现该接口,= 接口实现类)
*/
public interface ViewBinder<T> {

void bind(T target);
}

5.4 使用

public class MainActivity extends AppCompatActivity {

@BindView(R.id.tv)
TextView tv;
@BindView(R.id.tv)
TextView tv2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ButterKnife.bind(this);
}

@OnClick(R.id.tv)
public void click(View view) {
Toast.makeText(this, tv.getText().toString(), Toast.LENGTH_SHORT).show();
}

@OnClick(R.id.tv)
public void click2(View view) {
Toast.makeText(this, tv.getText().toString(), Toast.LENGTH_SHORT).show();
}
}

5.5 编译后生成的代码

public class MainActivity$ViewBinder implements ViewBinder<MainActivity> {
@Override
public void bind(final MainActivity target) {
target.tv = target.findViewById(2131165325);
target.tv2 = target.findViewById(2131165325);
target.findViewById(2131165325).setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View v) {
target.click(v);
}
} );
target.findViewById(2131165325).setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View v) {
target.click2(v);
}
} );
}
}


举报

相关推荐

0 条评论