APT简记

APT全称是:Annotation Processing Tool

1. 什么是注解:

从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

2. 注解的独立性

​ Annotation本身不影响程序代码的执行,保持其独立性。

3. 注解的分类:

(1) 元注解

​ 被用来定义注解的注解。

@Retention@Target@Documented@Inherited

(2) 标准注解

@Override,@Deprecated,@SuppressWarnings,@SafeVarargs,@FunctionalInterface

(3)自定义注解

​ 如何自定义一个注解?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package butterknife;

import androidx.annotation.IdRes;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Bind a field to the view for the specified ID. The view will automatically be cast to the field
*/
@Retention(RUNTIME) /**如果不写,默认是CLASS级别*/
@Target(FIELD) /**如果不写,默认是所有类型*/
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}

@Retention用来修饰这是一个什么类型的注解,以及APT在何时处理这个注解。

@Retention的三种类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,

/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,

/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}

RUNTIME级别的RetentionPolicy可以在CLASS级别使用吗?

@Target用来表示这个注解可以使用在哪些地方。比如:类、方法、属性、接口等。可修饰多个。

@Target类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 /*
* @since 1.5
*/
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,

/** Field declaration (includes enum constants) */
FIELD,

/** Method declaration */
METHOD,

/** Formal parameter declaration */
PARAMETER,

/** Constructor declaration */
CONSTRUCTOR,

/** Local variable declaration */
LOCAL_VARIABLE,

/** Annotation type declaration */
ANNOTATION_TYPE,

/** Package declaration */
PACKAGE,

/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,

/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}

4. 注解的定义

1
public @interface BindView

仅可用public 和缺省来修饰,不可用protectedprivate

5. 注解方法声明

1
@IdRes int value();

(1) 无参

(2) 方法不能抛异常;

(3) 返回值类型:

只能使用基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组。

(4) 默认值

​ 方法可以有默认返回值,没有默认返回值的方法必须在使用注解时设置值;

int value() default 0;

6. 注解的使用

1
@BindView(R.id.fab) FloatingActionButton fab;

7. 如何从注解中获取信息

(1) 通过反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void bind(final Activity target){
bindViews(target, target.getClass().getDeclaredFields(), target.getWindow().getDecorView()
}

private static void bindViews(final Object obj, Field[] fields, View rootView){
for(final Field field : fields) {
Annotation annotation = field.getAnnotation(BindView.class);
if (annotation != null) {
BindView bindView = (BindView) annotation;
int id = bindView.value();
View view = rootView.findViewById(id);
try {
field.setAccessible(true);
field.set(obj, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}

(2) 编译时注解

8.编译时注解的项目搭建

(1) 添加注解AptAnnotationLibrary(JavaLibrary),并添加注解:

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}

(2) 添加注解处理器AptProcessorLibrary(JavaLibrary)

1
2
3
4
5
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class BindProcessor extends AbstractProcessor {

}
1
2
> implementation 'com.google.auto.service:auto-service:1.0-rc4'
>

@AutoService(Processor.class)用于自动生成META-INF信息,告诉编译器处理该Processor,作用等同如下:
Screen Shot 2019-02-26 at 17.08.23

`@SupportedSourceVersion(SourceVersion.RELEASE_8)用于告诉编译器该Processor支持的版本

9. BindProcessor中需要执行的操作:

(1)告诉该处理器支持的注解:

1
2
3
4
5
6
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> result = new HashSet<>();
result.add(BindView.class.getCanonicalName());
return result;
}

(2) 处理编译器通知的注解信息:

1
2
3
4
5
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
parseBindElements(roundEnvironment.getElementsAnnotatedWith(BindView.class));
return doProcess();
}

(3)解析注解信息,并将注解信息放入elements集合中:

1
2
> private Map<TypeElement, Set<Element>> elements;
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void parseBindElements(Set<? extends Element> bindElements) {
if (bindElements == null || bindElements.isEmpty()) {
return;
}

for (Element element : bindElements) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
Set<Element> set = elements.get(enclosingElement);
if (set == null) {
set = new HashSet<>();
elements.put(enclosingElement, set);
}
set.add(element);
System.out.println("Add element :element name:" + element.getSimpleName() + "\tenclosingElement name:"+enclosingElement.getSimpleName());
}

(4) 处理注解信息并生成代码:

生成的代码:

1
2
3
4
5
6
7
8
9
> package com.csdroid.demo.apt;
>
> public final class MainActivityBind extends MainActivity {
> public static void bind(MainActivity activity) {
> activity.tvContent = (android.widget.TextView)activity.findViewById(2131230913);
> activity.fab = (android.support.design.widget.FloatingActionButton)activity.findViewById(2131230789);
> }
> }
>
1
2
3
4
5
6
7
8
9
10
11
12
13
private boolean doProcess() {
if (elements.isEmpty()) {
return false;
}
for (TypeElement enclosingElement : elements.keySet()) {
MethodSpec methodSpec = generateMethodSpec(enclosingElement);

TypeSpec typeSpec = generateTypeSpec(enclosingElement, methodSpec);

generateJavaFile(enclosingElement, typeSpec);
}
return true;
}
1
2
> implementation 'com.squareup:javapoet:1.10.0'
>

(5) 生成方法:

1
2
3
4
5
6
7
8
9
10
11
private MethodSpec generateMethodSpec(TypeElement enclosingElement) {
MethodSpec.Builder builder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(ClassName.get(enclosingElement.asType()), "activity")
.returns(TypeName.VOID);
Set<Element> bindSet = elements.get(enclosingElement);
for (Element element : bindSet) {
builder.addStatement(String.format(Locale.US,"activity.%s = (%s)activity.findViewById(%d)", element.getSimpleName(), element.asType(), element.getAnnotation(BindView.class).value()));
}
return builder.build();
}

(6) 生成类:

1
2
3
4
5
6
7
private TypeSpec generateTypeSpec(TypeElement enclosingElement, MethodSpec methodSpec) {
return TypeSpec.classBuilder(enclosingElement.getSimpleName() + "Bind")
.superclass(TypeName.get(enclosingElement.asType()))
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
.addMethod(methodSpec)
.build();
}

(7) 生成同包名下的java文件:

1
2
3
4
5
6
7
8
9
10
private boolean generateJavaFile(TypeElement enclosingElement, TypeSpec typeSpec) {
JavaFile javaFile = JavaFile.builder(elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString(), typeSpec).build();
try {
javaFile.writeTo(processingEnv.getFiler());
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}

此时将AptProcessor加入App依赖库后编译即可看到生成的代码;

代码目录:.AptDemo/app/build/generated/source/apt/debug/com/csdroid/demo/apt/MainActivityBind.java

1
2
> annotationProcessor project(':AptProcessor')
>

(8) 如何使用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainActivity extends AppCompatActivity {

@BindView(R.id.fab)
public FloatingActionButton fab;

@BindView(R.id.tv_content)
public TextView tvContent;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bind.bind(this);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
tvContent.setText("Action");
}
});
}
}

此部分是BindView在App中的使用

(9) 如何建立App和生成代码间的联系?

创建AptApi android library,并提供bind接口供外部调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Bind {
public static Map<Class, Method> binds = new HashMap<>();
public static void bind(Activity activity) {
if (activity == null || activity.isFinishing()) {
return;
}
Method method = binds.get(activity.getClass());
if (method == null) {
Package pack = activity.getClass().getPackage();
if (pack == null) {
return;
}
String className = pack.getName() + "." + activity.getClass().getSimpleName() + "Bind";
try {
Class bindClass = Class.forName(className);
method = bindClass.getMethod("bind", activity.getClass());
binds.put(activity.getClass(), method);
} catch (Exception e) {
e.printStackTrace();
}
}
try {
if (method != null) {
method.invoke(null, activity);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

App工程依赖AptApi library

implementation project(':AptApi')

Demo源码:https://github.com/csIng/BindView

0%