APT工具
APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,所以如果想要自定义的注解处理器能够正常运行,必要要通过APT工具来进行处理。
也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能执行。
这里使用的APT工具是annotationProcessor,这个是Android Gradle插件2.2版本发布后,Android 官方提供了annotationProcessor来代替android-apt的APT工具,关于annotationProcessor和android-apt以及其他一些常见的
概述
现在Android开发中许多流行的第三方库都使用了注解生成代码的方式,例如 ButterKnife, Dagger2,Glide等等
Java 是从 Java 5 开始支持注解的,是其很重要的语言特性,然而其潜力的发掘也仅仅是近几年的事情。Java 5 真的是Java 一个里程碑式的版本,各种重要特性均源自此版本。
注解一般有两种方式处理方式:
第一:运行时获取并使用注解信息,此方式属于反射范畴,有性能损失,例如Retrofit2等。
第二:编译时获取注解信息,并据此产生java代码文件,无性能损失,例如ButterKnife, Dagger2等。
annotationprocessor编译器,在编译期间创建的需要用到
1 java注解的基本知识,
2 java代码生成库 javapoet
自定义ButterKnife
1:项目结构
整个项目主要有如下四个部分
- app : Android 项目主module,是一个 android application
- butterknife-annotations: 定义了所有的注解, 是一个 java library
- butterknife-compiler: 处理相关注解,关键的代码都在这个module里,是一个 java library。因为在使用自定义AbstractProcessor需要使用到javax包中的相关类和接口,这个在android库中并不存在,所以需要使用到Java库。
- butterknife: 处理生成代码的调用,是一个android library
因为自定义AbstractProcessor的作用是在编译时生成相关的java源文件的,在程序中最后需要的是他生成的Java文件,最后打包进apk也是他生成的文件,butterknife-compiler本身是不需要打包的。
但是在butterknife-compiler和butterknife中需要对具体的注解类进行操作,需要引用butterknife-annotations库,所以将具体的注解放在一个库butterknife-annotations中便于引用
2:定义注解 butterknife-annotations
在工程中添加一个java library类型的module,取名butterknife-annotations
Android Studio -> file -> new module -> java library
@BindView 在这个库中主要用于声明具体的注解的,
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
3:定义注解处理器 butterknife-compiler
新建一个module,取名为butterknife-compiler,类型必须为java library,如下所示
Android Studio -> file -> new module -> java library
在butterknife-compiler库中,主要的功能就是自定义AbstractProcessor,在编译时生成具体的Java文件实现依赖注入功能。因为该注解处理器需要使用到指定注解,所以该库需要依赖butterknife-annotations。
关于AbstractProcessor类中个各个方法在上面已经提到过,这里主要说一下process方法的具体功能。
在process方法中我们主要有两个功能需要完成:
获取同一个类中的所有指定注解修饰的Element;
在process方法中,有一个参数RoundEnvironment,通过该参数可以通过遍历获取代码中所有通过指定注解(这里只@BindeView)修饰的Element对象。通过Element对象可以获取字段名称,字段类型以及注解元素的值。
创建Java文件;
将同一个类中通过指定注解修饰的所有Element在同一个Java文件中实现初始化,这样做的目的是让在最终依赖注入时便于操作。
Processor 一般会重写父类的4个方法:
init:
初始化工作,我们可以得到一些有用的工具,例如 Filer,我们需要它将生成的代码写入文件中
process:
最重要的方法,所有的注解处理都是在此完成
getSupportedAnnotationTypes:
返回我们所要处理的注解的一个集合
getSupportedSourceVersion:
要支持的java版本
下面是自定义AbstractProcessor中的具体逻辑:
@AutoService(Process.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private Messager messager;
private Elements elementUtils;
private Filer filer;
private Types typeUtils;
/**
* `
* 初始化操作
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
typeUtils = processingEnvironment.getTypeUtils();
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
}
/**
* 返回此Porcessor可以处理的注解操作
*
* @return
*/
@Override
public Set<String> getSupportedOptions() {
return super.getSupportedOptions();
}
/**
* 返回此注释 Processor 支持的最新的源版本
* <p>
* 该方法可以通过注解@SupportedSourceVersion指定
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 返回此 Processor 支持的注释类型的名称。结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 " name.*" 形式的名称,表示所有以 " name." 开头的规范名称的注释类型集合。最后, "*" 自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 "*",除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。
* <p>
* 该方法可以通过注解@SupportedSourceVersion指定
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new LinkedHashSet<>();
set.add(BindView.class.getCanonicalName());
return set;
}
/**
* 注解处理器的核心方法,处理具体的注解
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 通过roundEnvironment扫描所有的类文件,获取所有存在指定注解的字段
Map<TypeElement, List<FieldViewBinding>> targetMap = getTargetMap(roundEnvironment);
createJavaFile(targetMap.entrySet());
return false;
}
/**
* 获取所有存在注解的类
*
* @param roundEnvironment
* @return
*/
private Map<TypeElement, List<FieldViewBinding>> getTargetMap(RoundEnvironment roundEnvironment) {
/**
* 键:TypeElement,指定Activity;
* 值:List<FieldViewBinding>,activiyt中所有的注解修饰的字段
*/
Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>();
// 1、获取代码中所有使用@BindView注解修饰的字段
Set<? extends Element> annotatedElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : annotatedElements) {
// 获取字段名称 (textView)
String fieldName = element.getSimpleName().toString();
// 获取字段类型 (android.widget.TextView)
TypeMirror fieldType = element.asType();
// 获取注解元素的值 (R.id.textView)
int viewId = element.getAnnotation(BindView.class).value();
// 获取声明element的全限定类名 (com.zhangke.simplifybutterknife.MainActivity)
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
List<FieldViewBinding> list = targetMap.get(typeElement);
if (list == null) {
list = new ArrayList<>();
targetMap.put(typeElement, list);
}
list.add(new FieldViewBinding(fieldName, fieldType, viewId));
}
return targetMap;
}
/**
* 创建Java文件
* @param entries
*/
private void createJavaFile(Set<Map.Entry<TypeElement, List<FieldViewBinding>>> entries) {
for (Map.Entry<TypeElement, List<FieldViewBinding>> entry : entries) {
TypeElement typeElement = entry.getKey();
List<FieldViewBinding> list = entry.getValue();
if (list == null || list.size() == 0) {
continue;
}
// 获取包名
String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
// 根据旧Java类名创建新的Java文件
String className = typeElement.getQualifiedName().toString().substring(packageName.length() + 1);
String newClassName = className + "_ViewBinding";
MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.bestGuess(className), "target");
for (FieldViewBinding fieldViewBinding : list) {
String packageNameString = fieldViewBinding.getFieldType().toString();
ClassName viewClass = ClassName.bestGuess(packageNameString);
methodBuilder.addStatement
("target.$L=($T)target.findViewById($L)", fieldViewBinding.getFieldName()
, viewClass, fieldViewBinding.getViewId());
}
TypeSpec typeBuilder = TypeSpec.classBuilder(newClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(methodBuilder.build())
.build();
JavaFile javaFile = JavaFile.builder(packageName, typeBuilder)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
build.gradle配置:
apply plugin: 'java-library'
dependencies {
// 引入注解
compile project(':butterknife-annotations')
//google提供的auto-service开源库
compile 'com.google.auto.service:auto-service:1.0-rc3'
// compile 'com.google.auto:auto-common:0.8'
//java 代码生成
compile 'com.squareup:javapoet:1.8.0'
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
注解处理器的配置
在上面基本就实现了一个自定义注解处理器,但是如果想要注解处理器能够在编译器生成代码,这里还需要做一个配置说明,这里有两种配置方法:
第一种方法:
在butterknife-compiler的main目录下创建目录resources/META-INF/services,并在该目录下创建文件javax.annotation.processing.Processor,在该文件中写入注解处理器的全限定类名(这里是com.zhangke.compiler.ButterKnifeProcessor)
第二种方法:
如何感觉通过上面的方法配置特别麻烦,这里也可以通过google提供的auto-service开源库。通过该方式我们只需要在注解处理器上添加注解@AutoService(Processor.class)即可。
4:定义初始化模块 butterknife
新建一个module,取名为butterknife,类型必须为android library,如下所示
Android Studio -> file -> new module -> android library
butterknife是一个android库工程,该库的作用主要就是完成依赖注入。在该库中会依赖butterknife-annotations
butterknife中定义的用于实现依赖注入的工具类
public class ButterKnife {
public static void bind(Activity activity) {
//1、获取全限定类名
String name = activity.getClass().getName();
try {
//2、 根据全限定类名获取通过注解解释器生成的Java类
Class<?> clazz = Class.forName(name + "_ViewBinding");
//3、 通过反射获取构造方法并创建实例完成依赖注入
clazz.getConstructor(activity.getClass()).newInstance(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5:使用
需要在项目中也就是在app/build.gradle文件中添加库依赖并且声明APT工具,如下:
dependencies {
// 省略
// 添加butterknife依赖 grade3.0以上才能用
implementation project(':butterknife')
annotationProcessor project(':butterknife-compiler')
}
6:测试
在项目主工程中定义了MainActivity,在这Activity中使用注解@BindView,如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.button)
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 通过工具类实现依赖注入
ButterKnife.bind(this);
textView.setText("hahhah");
}
}
通过Android Studio的Rebuild Project,或者直接运行,就可以看到在主app工程build/generated/source/apt/debug目录下就已经生成了新的Java文件MainActiivty_ViewBinding
这里可以看看MainActivity_ViewBinding类中的具体内容,发现在该文件中为MainActivity_ViewBinding创建了一个构造方法,该构造方法中需要传入指定的activity,然后在该构造方法中就可以完成button和textView的初始化即findViewByid的过程。
参考:https://blog.csdn.net/kaifa1321/article/details/79683246
https://blog.csdn.net/ShuSheng0007/article/details/90734159