学习扔物线进阶视频课程笔记。注解处理器APT
元注解
元注解,就是用来注解自定义注解的注解。
有6个,@Target、@Retention、@Documented、@Inherited、@Repeatable、@Native
@Target,目标,表示自定义注解修饰的目标,用来描述注解的使用范围。常用参数有ElementType.TYPE(表示注解可以修饰类、接口、枚举、注解类),ElementType.FIELD(表示注解可以修饰字段),ElementType.METHOD(表示注解可以修饰方法)。
@Retention,保留级别,表示需要在什么级别保存该注解信息,用来表述注解保留的时间范围。可选参数有RetentionPolicy.SOURCE(只会保留在源文件,编译器会将注解丢弃),RetentionPolicy.CLASS(注解会被编译器记录在class文件中,在虚拟机运行时没有注解信息),RetentionPolicy.RUNTIME(注解信息会被保留到运行时,因此可以被反射读取)。
@Documented,文档记录,描述在使用javadoc工具为类生成帮助文档时,是否要保留其注解信息。
@Inherited,继承,使被他修饰的注解具有继承性,自定义注解被@Inherited修饰后,使用自定义注解去注解类时,子类会自动继承此注解。
@Repeatable,可重复,使被他修饰的注解可以在相同的程序元素中重复使用,java8新增。
@Native,使用@Native修饰成员变量,表示这个变量可以被本地代码引用,常被代码生成工具使用,java8新增。
自定义注解源码:
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
}
反射实现ButterKnife
- 创建@BindView注解
- 用反射获取Field[],然后找到加了@BindView 的字段,自动调用findViewById来绑定对象
代码:
//注解类
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
//Binder类
public class Binder {
public static void bind(Activity activity) {
Log.d("JmBinder", "JmBinder.bind start");
for (Field field : activity.getClass().getDeclaredFields()) {
BindView jmBindView = field.getAnnotation(BindView.class);
if (jmBindView != null) {
try {
field.setAccessible(true);
field.set(activity, activity.findViewById(jmBindView.value()));
Log.d("JmBinder", "JmBinder.bind success. field=" + field);
} catch (Exception e) {
}
}
}
}
}
//在activity的使用
@BindView(R.id.btn_retrofit)
lateinit var btn_retrofit: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Binder.bind(this)
btn_retrofit.setOnClickListener {
startActivity(Intent(this@MainActivity, RetrofitActivity::class.java))
}
}
可以看到,使用简单的几行代码,就可以实现一个简化版本的ButterKnife。但是用这种方式实现有性能问题,反射遍历activity的所有字段,找到注解修饰的字段后,再反射赋值,会随着字段的增多和注解的增多,bind的耗时也会增多。
依赖注入
什么是依赖注入?把依赖的决定权交给外部,就是依赖注入。
Dagger:外部的依赖图来决定依赖的值,对象自己只负责索要,而不负责指定值,所以是依赖注入。
ButterKnife:类自己决定依赖的获取,只把执行过程交给ButterKnife,所以只是一个视图绑定,而不是依赖注入。
APT
APT:Annotation Processing Tool,注解处理器。
原理:编译过程中读取源码,然后生成新的代码和文件,再放在一起进行编译。
思路:每个使用@BindView注解修饰字段的Activity,都通过APT生成一个ActivityBinding类,然后在将findViewById的代码添加到ActivityBinding类的构造方法中,在activity中调用Binder.bind时,通过反射创建ActivityBinding类对象,就等同于调用所有的findViewById。
APT实战
在wifikit中,通过APT的方式将所有的页面id类添加到数组,生成代码,初始化时通过反射拿到所有的id类。
注解类:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
@Inherited
public @interface WifikitUi {
}
AnnotationProcessor项目结构:
Processor代码:
//@AutoService(Processor.class) //使用auto-service添加Processor
public class WifikitUiProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(WifikitUi.class.getCanonicalName());
}
private Filer filer = null;
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
System.out.println("WifikitUiProcessor init filer=" + filer);
}
private static final String PACK_NAME = "com.android.gps.tracker.wifikit.ui";
private static final String CLASS_NAME = "InterfaceIds2";
private static final Set<String> ids = new HashSet<>();
private static boolean created = false;
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
System.out.println("WifikitUiProcessor process start");
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(WifikitUi.class);
for (Element element : elements) {
// System.out.println("WifikitUiProcessor WifikitUi element.getClass=" + element.getClass());
Symbol symbol = (Symbol) element;
boolean isInterface = symbol.isInterface();
boolean isAbstract = symbol.getModifiers().contains(Modifier.ABSTRACT);
if (!isInterface && !isAbstract) {
String qualifiedName = symbol.getQualifiedName().toString();
ids.add(qualifiedName);
System.out.println("WifikitUiProcessor WifikitUi qualifiedName=" + qualifiedName);
}
}
createFile();
return true;
}
private void createFile() {
if (created) {
return;
}
created = true;
System.out.println("WifikitUiProcessor createFile start. ids.size=" + ids.size());
try {
ClassName className = ClassName.get(PACK_NAME, CLASS_NAME);
FieldSpec idList = FieldSpec.builder(ArrayList.class, "idList")
.addModifiers(Modifier.PUBLIC)
.build();
String initList = " idList=new ArrayList<>();\n";
StringBuilder addId = new StringBuilder();
for (String id : ids) {
addId.append(" idList.add(\"").append(id).append("\");\n");
}
MethodSpec constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addCode(initList)
.addCode(addId.toString())
.build();
TypeSpec builtClass = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.addField(idList)
.addMethod(constructor)
.build();
JavaFile javaFile = JavaFile.builder(PACK_NAME, builtClass).build();
javaFile.writeTo(filer);
// try {
// if (filer instanceof JavacFiler) {
// JavacFiler javacFiler = (JavacFiler) filer;
// javacFiler.close();
// }
// } catch (Exception ee) {
//
// }
System.out.println("WifikitUiProcessor createFile end");
} catch (Exception e) {
System.out.println("WifikitUiProcessor createFile e=" + e);
}
}
}
//gradle配置
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation project(path:':jm-annotations')
// implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.13.0'
}
在Processor中使用javapoet库来生成java代码
申明注解有两种方式,1是通过配置javax.annotation.processing.Processor路径实现,2是使用@AutoService注解实现
app项目中使用APT的gradle配置
dependencies {
//apt处理注解
implementation project(path:':jm-annotations')
annotationProcessor project(path:':jm-apt')
}
app项目中的注解使用如下:
自动生成的代码如下:
package com.android.gps.tracker.wifikit.ui;
import java.util.ArrayList;
public class InterfaceIds2 {
public ArrayList idList;
public InterfaceIds2() {
idList=new ArrayList<>();
idList.add("com.android.gps.tracker.wifikit.ui.ids.ID11");
}
}