[TOC]
Annotaion Processing Tool(注解处理工具)
Java为了提高其扩展性,提供了反射机制可以处理注解。其实我们还可以在编译时处理注解,这就要用到官方为我们提供的注解处理工具APT(Annotaion Processing Tool).
为了提高易读性,我们先来看以下一段代码
public class MainActivity extends Activity{
public void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.tv);
textView.setText("Hello World");
}
}
这段代码非常直观,通俗易懂。但是每次都写这些无聊的代码却是也降低了我们做开发的乐趣,同时也降低了开发的效率。再来看以下一段代码(ButterKnife Dagger2..)
public class MainActivity extends Activity{
@BindView(R.id.tv)
TextView textView
public void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
注:ButterKnife已经在8.0.1后更改为编译时处理注解(APT)(具体版本不清楚,反正7.0.1版本还没有这么做)
这两段代码比对后,已经有了比较明显的趋向性
- BindView
- Click
@BindView(R.id.tv)
TextView textView;
@Click(R.id.btn)
public void show(View view){
// do sth
}
3. 生成的代理类(实际就是我们没有写的那些findViewById)
public class MainActivity$$Proxy implements IProxy<MainActivity>{
public void inject(MainActivity activity,View root){
target.textView = (TextView)root.findViewById(R.id.tv);
...
root.findViewById(R.id.btn)
.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View view){
show(view);
}
})
}
}
注:上边这段代码需要通过代码生成
- annotation --------声明的注解Module(Java项目)
- compiler -------- 解析注解的Module(Java项目),不需要打包到apk中
- api -------- 关联项目与compiler 的Module(一般为Android项目,原因主要是涉及 Activity/View/Fragment 组件)
- app ====== 我们自己的项目
Q: 为什么要使用三个Module来实现
A: compiler是不需要打包进去的,它的目的仅仅是在build的时候帮助我们创建代理类,也可以理解为帮我们写我们不想写的代码
Q: 这几个Module的依赖关系是怎样的
A: api 依赖 Annotation
compiler 依赖Annotation
app 依赖 Annotation/api/ apt project(':compiler')
- 创建Java项目
- 创建两个注解
@Target({
ElementType.FIELD
})
@Retention(
RetentionPolicy.CLASS
)
public @interface BindView {
int value() default View.NO_ID;
}
@Target({
ElementType.METHOD
})
@Retention(
RetentionPolicy.CLASS
)
public @interface Click {
int value() default View.NO_ID;
}
注: View.NO_ID = -1; 默认的控件id
- compile 'com.squareup:javapoet:1.7.0'
- compile 'com.google.auto.service:auto-service:1.0-rc2'
注:javapoet 是square 公司开发的能够快速生成java类的库
auto-service 是为了帮助我们实现 META-INF/services/javax.annotation.processing.Processor 文件
- compiler 需要依赖Annotation 的Module
- 创建一个新的类继承AbstractProcessor
- 给当前Processor添加注解@AutoService(Processor.class)
- 此时可以省去创建META-INF/services.javax.annotation.processing.Processor配置文件
@AutoService(Processor.class)
pulic class DggProcessor extends AbstractProcessor{
}
- init方法(初始化)
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
}
- elementUtils 根据元素获取当前元素的其他信息(包名 注释)
- filer 生成代码的目录 这里默认为 app(当前项目)build/generated/source/apt/debug 目录
- messager 在Gradle Console输出build过程中的日志信息
-
getSupportedAnnotationTypes方法
支持解析的注解类型
-
getSupportedSourceVersion方法
支持jdk编译的版本一般为
public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }
-
process方法
AbstractProcessor的核心方法,用于生成对应的代理类
- 直观解读
public class MainActivity extends Activity{ @BindView(R.id.tv) TextView textView; public void onCreate(Bundle onSaveInstance){ super.onCreate(onSaveInstance); setContentView(R.layout.activity_main); // Note: 这里还没有做注入 //在添加完@BindView的注解后,直接在build中点击rebuild } }
将上面的代码自己生成自己的代理类。大概是↓这个样子
public class MainActivity$$Proxy implements IProxy<MainActivity>{ public void inject(MainActivity activity,View root){ activity.textView = (TextView)root.findViewById(R.id.tv); ... } }
注:此时生成的代理类是自己的项目是相互独立的
这个是为了以后关联项目与代理类的时候方便调用inject方法做注入
- 实现
public interface IProxy<T>{ public void inject(T target,View root); }
代理类生成
Example
package com.example.helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } }
↓
MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build(); javaFile.writeTo(System.out);
在注解处理器的世界里,整个java代码被结构化了,每一部分都是一个Element对象,也就是说每一部分都被当做一个对象看待
package com.example; //PackageElement public class Foo { // TypeElement private int a; // VariableElement private Foo other; // VariableElement public Foo() {} // ExecuteableElement public void setA( // ExecuteableElement int newA // TypeElement ) { } }
- ExecuteableElement:可以表示一个普通方法、构造方法、初始化方法(静态和实例)。
- PackageElement:代表一个包名。
- TypeElement:代表一个类、接口。
- VariableElement:代表一个字段、枚举常量、方法或构造方法的参数、本地变量、或异常参数等。
- Element:上述所有元素的父接口,代表源码中的每一个元素。
你可以通过以下两个方法获取一个元素的父元素和子元素
Element getEnclosingElement(); //获取父元素 List<? extends Element> getEnclosedElements(); //获取子元素
- 一个Field的父元素是当前类
- 一个类的子元素是当前类的方法和字段
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
Set<? extends Element> bindViewElements = env.getElementsAnnotatedWith(BindView.class);
for (Element element : bindViewElements) {
//BindView Field Parameter
boolean valid = isValid(BindView.class, element);
// messager.printMessage(Diagnostic.Kind.ERROR,"process...."+bindViewElements.size()+"::"+valid);
if (!valid) {
return true;
}
processBindView(element);
}
return false;
}
- 获取被BindView注解的Element对象(当前支持注解Field)
env.getElementsAnnotatedWith(BindView.class);
当前获取到的Element为VariableElement对象