0
点赞
收藏
分享

微信扫一扫

java APT原理及APT实战 - 一步步教你写ButterKnife

一、定义

Java APT 是 Java 技术设计的一个 APT 架构,

APT(Annotation Processing Tool)即注解处理器,它是一种处理注解的工具,也是javac中的一个工具,用于在编译阶段未生成class之前对源码中的注解进行扫描和处理。

APT可以用来在编译时扫描和处理注解, 它可以用来获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。 在Android中有如ButterKnife、Dagger、EventBus等第三方框架,都采用了APT。

常用的使用方式是这样的:

二、APT工作原理

Java Annotations Processing Tool(APT)是一个预处理器,可以在Java代码编译期间读取注解,并生成相关的代码。

它的工作原理如下:

  1. Java编译器会将源文件传递给APT进行处理;
  2. APT会扫描源文件中所有的注解,并找到对应的处理器;
  3. 处理器会对注解进行处理,并生成新的Java代码文件(或其他文件);
  4. 生成的Java代码文件被编译成字节码文件;
  5. 编译器将生成的字节码文件和原始Java代码文件一起打包成jar包或class文件。

APT通过Java标准类库中的javax.annotation.processing包提供注解处理的框架。注解处理器必须实现该包中的特定接口,这些接口定义了APT框架的核心功能。通过实现接口,注解处理器能够直接访问来自编译器的数据,以及用于注解处理的元数据信息。注解处理器总是运行在 Java 编译环境中。

APT的使用可以帮助简化一些重复、冗杂的代码生成工作。

三、APT实战1(运行时注解) - 一步步教你写ButterKnife

使用运行时注解的方式实战简单APT,这个比较简单,就是用反射的方式来实现,

void injectLayout(Context context) {
// 1. 获取当前class
Class<?> clazz = context.getClass();
// 2. 根据class获取class上面的注解
InjectContenttLayout annotation = clazz.getAnnotation(InjectContenttLayout.class);

// 3. 获取注解中布局文件的id的值
int layoutId = annotation.value();
try {
// 4. 获取activity中的setContentView方法
Method method = clazz.getMethod("setContentView", int.class);
// 5. 执行setContentView方法,传入layoutId参数
method.invoke(context, layoutId);
} catch (Exception e) {
}
}

四、APT实战2(编译时注解) - 一步步教你写ButterKnife

使用编译时注解的方式实战简单APT,手写ButterKnife框架,我们来写一下布局文件view的注入,比如我们不想写烦人的findViewById方法,直接用个注解来搞定,

本 Demo 下载

其核心思想是java的ioc(inversion of control),也叫di(dependency injection,依赖注入),是一种面向对象编程中的设计模式。下面我们开始

4.1 创建一个项目,如下图

4.2 新建自定义注解

创建一个Java Library Module名称叫 apt-annotation

在这个module中创建自定义注解 @BindView,如下图

4.3 实现APT Compiler处理注解

创建一个Java Library Module名称叫 apt-compiler-processor,并添加注解module依赖

dependencies {
implementation project(':apt-annotation')
}

这个module的作用主要是用来处理注解,并生成java帮助类文件,拆解步骤为

如下图所示

该module在处理注解时,必须继承AbstractProcessor抽象类,入口类为process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)


/**
* 注解处理方法,
*
* @param set 注解的类型集合
* @param roundEnv 运行环境
* @return
*/

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
System.out.println("start process");
if (set != null && set.size() != 0) {
//1、 扫描所有被注解标记的Element,获得被BindView注解标记的element
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);

categories(elements);

for (TypeElement typeElement : mToBindMap.keySet()) {
// 获取帮助类所有代码
String code = generateCode(typeElement);
// 构建要生成的帮助类的类名
String helperClassName = typeElement.getQualifiedName() + "_ButterKnifeTest"; //

System.out.println("start process 帮助类的类名= " + helperClassName);

// 输出帮助类的java文件,
// 在本例中就是MainActivity_ButterKnifeTest.java文件
// 输出的文件在build->generated-ap_generated_sources->debug->out->包名目录下
try {
System.out.println("生成帮助类 ");
JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
Writer writer = jfo.openWriter();
writer.write(code);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}

}
return true;
}
return false;
}

当代码写完后,就需要注册APT(如上图所示)

注册一个APT需要以下步骤:

4.4 以上步骤全部完成后, 还需要对外提供API(当然也可以不拆分)

创建一个Android Library Module,名称叫apt-api,并添加依赖

dependencies {
...

api project(':apt-annotation')
}

 实现很简单,就是通过反射去调用APT生成的帮助类的方法去实现View的自动绑定,部分代码如下:


public void inject(Object target) {
String className = target.getClass().getCanonicalName();
String helperName = className + "_ButterKnifeTest";
System.out.println("ButterKnifeTest inject" + helperName);
try {
IBindHelper helper = (IBindHelper) (Class.forName(helperName).getConstructor().newInstance());
helper.inject(target);
} catch (Exception e) {
e.printStackTrace();
}
}

最后就是使用了,在app module里添加依赖

dependencies {
...
annotationProcessor project(':apt-compiler-processor')
implementation project(':apt-api')
}

4.5 使用如下:

public class MainActivity extends AppCompatActivity {

@BindView(value = R.id.test_textview)
public TextView testTextView;

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

ButterKnifeTest.getInstance().inject(this);
testTextView.setText("手写 butterknife demo");
}
}

运行代码截图

 

五、痛点及优化

我们可以看到,按官方文档一步步来写apt比较繁琐,

针对以上问题:

1、我们可以使用JavaPoet来替代拼接字符串( JavaPoet是一个用来生成Java代码的框架,对JavaPoet不了解的请自行学习)

官网地址: GitHub - square/javapoet: A Java API for generating .java source files.

2、使用Auto-Service来自动注册APT

这是谷歌官方出品的一个开源库,可以省去注册APT的步骤,只需要一行注释
先在apt-compiler模块中添加依赖

dependencies {
...

implementation 'com.google.auto.service:auto-service:1.0-rc2'
}

然后添加注释即可,如下图所示: 

六、一些疑问

5.1 手写注解处理器时,注解处理器processor为什么要在META-INFO注册?

META-INFO相当于一个信息包,用于存放一些meta information相关的信息,用来配置应用程序、扩展程序、类加载器和服务manifest.mf文件,在编译时,java编译器回去该文件中查找实现了AbstractProcess的子类,就相当于注册。

5.2  APT(Annotation Processing Tool)如何调用AbstractProcess的呢?(注解处理器是如何被系统调用的?)

annotationProcessor 指定apt处理器。

5.3 安卓中,APT项目会不会增加apk的体积?

不会,processor的作用是在编译器解析注解、生成文件等,只在编译器用到,是不会打包进apk的。

更高级的用法可自行阅读开源项目,向大佬学习~

Demo 下载 :https://download.csdn.net/download/fumeidonga/87767415

举报

相关推荐

0 条评论