一、APT是什么
The command-line utility apt, annotation processing tool, finds and executes annotation processors based on the annotations present in the set of specified source files being examined. The annotation processors use a set of reflective APIs and supporting infrastructure to perform their processing of program annotations (JSR 175). The apt reflective APIs provide a build-time, source-based, read-only view of program structure. These reflective APIs are designed to cleanly model the JavaTM programming language's type system after the addition of generics (JSR 14). First, apt runs annotation processors that can produce new source code and other files. Next, apt can cause compilation of both original and generated source files, thus easing the development cycle.
上方是官方的解析,通俗讲APT(Annotation Processing Tool),是javac的一个工具,中文意思为编译时注解处理器:
- 可以用来在编译时扫描和处理注解
- 通过获取到注解和被注解对象的相关信息,根据需求来自动的生成一些代码
二、基本使用(不含Auto-Service)
1. 在android项目中添加一个java module: libannotation,用于定义注解
// BindView.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)// 作用于属性
public @interface BindView {
int viewId() default 0;
}
// BindActivity.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface BindActivity {
}
2. 在android项目中再添加另外一个java module: libapt,用于编写注册自定义的注解处理器(即处理BindView和BindActivity两个注解)
2.1 这里我们需要在该module中引用到一个第三方库:com.squareup:javapoet(JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件)
在build.gradle增加引用,因为我们需要处理自定义的注解,需要将ligannotation也引用进来
dependencies {
// JavaPoet
implementation 'com.squareup:javapoet:1.13.0'
implementation project(":libannotation")
}
2.2 定义注解处理器,通过继承AbstractProcessor
可以看到提供了几个核心方法
- init:ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer, 通过传入的processingEnv进行初始化
- getSupportedSourceVersion:指定使用的java版本
- getSupportedAnnotationTypes:指名该注解处理器提供给哪些注解使用,这里就填写我们自定义的注解
- process:主要在这里扫描、处理注解
public class ViewProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindActivity.class.getCanonicalName());
types.add(BindView.class.getCanonicalName());
return types;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindActivity.class);
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element;
List<? extends Element> members = elementUtils.getAllMembers(typeElement);
MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindActivity")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.VOID)
.addParameter(ClassName.get(typeElement.asType()), "currentActivity");
for (Element item : members) {
BindView bindView = item.getAnnotation(BindView.class);
if (bindView == null) {
continue;
}
bindViewMethodSpecBuilder.addStatement(String.format("currentActivity.%s = (%s) currentActivity.findViewById(%s)", item.getSimpleName(), ClassName.get(item.asType()).toString(), bindView.viewId()));
}
TypeSpec typeSpec = TypeSpec.classBuilder("Bind" + element.getSimpleName())
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(bindViewMethodSpecBuilder.build())
.build();
JavaFile javaFile = JavaFile.builder(elementUtils.getPackageOf(typeElement).getQualifiedName().toString(), typeSpec).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
}
2.3 指定注解处理器(缺少了这一步,将不会有任何输出)
- 在当前 module 的 main 目录下新建 resources 文件夹
- 在 resources 文件夹下新建 META-INF/services 文件夹
- 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件
- 在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径
3. 在主 module:app 中使用
3.1 引用 libapt 库和 libannotaion 库
dependencies {
....
implementation project(":libannotation")
annotationProcessor project(":libapt")
}
3.2 在MainActivity中使用相应注解
@BindActivity
public class MainActivity extends AppCompatActivity {
@BindView(viewId = R.id.text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
3.3 rebuild项目
在 app\build\generated\ap_generated_sources\debug\ (使用旧版的gradle可能生成的文件夹不同:#build/generated/source/apt/debug)目录下生成了一个 BindMainActivity, 该Activity生成的格式就是按照我们在Processor的process中定义的格式
3.4 使用该Activity
@BindActivity
public class MainActivity extends AppCompatActivity {
@BindView(viewId = R.id.text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindMainActivity.bindActivity(this);
textView.setText("change the text success!");
}
}
3.5 项目地址
三、补充
这里有一点要注意的是:不能将libannotation和libapt合并到同一个module,然后在 app module中直接引用~你会发现这样子处理不能起效
这里还有一个问题值得思考,每次指定自定义注解处理器,都需要经过这么繁琐的步骤吗?创建一系列的文件夹,然后指定全路径的注解处理器?有没有简单的方式?
四、Auto-Service的使用
答案是有的。google 的 AutoService 可以帮我们自动完成指定步骤,生成相应的文件。官方文档
本章的demo实现和上一篇的实现类似
1. 在 libapt 的build.gradle中引入AutoService
dependencies {
// AutoService相关
implementation 'com.google.auto.service:auto-service:1.0'
annotationProcessor 'com.google.auto.service:auto-service:1.0'
implementation 'com.google.auto:auto-common:1.0'
implementation 'com.squareup:javapoet:1.13.0'
implementation project(path: ':libannotation')
}
2. 在自定义注解处理器中添加注解
@AutoService(Processor.class)
public class ViewProcessor extends AbstractProcessor {
...
}
3. 移除上一张手动创建的文件夹和文件,要不然会报错
Execution failed for task ':libapt:jar'.
> Entry META-INF/services/javax.annotation.processing.Processor is a duplicate but no duplicate handling strategy has been set.
4. rebuild一下
5. 项目地址
五、思考
通过上述的流程,我们已经可以省去一堆的 findViewById 来查找控件了。这只是一个极简单的版本,相比起来,ButterKnife就强大得多:Click事件处理功能、Fragment中使用、支持ViewHolder绑定等。当然,理解了这些基本原理后,我们也可以尝试去扩充其他的功能。