首先我们来了解apt的定义;它是干什么用的?和Java AbstractProcessor来实现ButterKnife的自定义。
APT篇:
Annotation processing Tool简称: APT即注解处理器,它是一种处理注解的工具,也是javac中的一个工具。APT可以用来在编译时扫描和处理注解。 这类API的使用被广泛的用于各种框架,如dubbo,lombok等。
APT作用:
获取到注解和被注解对象的相关信息可以通过APT 实现;然后拿到这些信息后我们可以根据需求来自动的生成一些代码,就省去了手动编写。举例:如 ButterKnife、Dagger 等三方框架在Android中,都是采用APT。
两种处理方式:
Spring和Spring Boot : 最常见也是最显式化的就是Spring以及Spring Boot的注解实现了,在运行期容器启动时,根据注解扫描类,并加载到Spring容器中。而另一种就是本文主要介绍的注解处理,即编译期注解处理器,用于在编译期通过JDK提供的API,对Java文件编译前生成的Java语法树进行处理,实现想要的功能。
使用注解处理器:
第一步我们来创建java库,在库中定义自己的注解,如:
@Documented
@Target( {ElementType.FIELD})
@Retention(value = RetentionPolicy . RUNTIME )
public @interface TestSelfAnnotation {
int value() default 0;
}
第二步 创建java库后,添加如下依赖,编写自己的注解处理器继承 AbstractProcessor ,生成代码使用 JavaPoet 库,生成.java源文件。
@AutoService(Processor.class) public class MyAnnotationProcessor extends AbstractProcessor {
private Map
private Messager getMessagers;
private Filer getFilers;
private Elements elementUtils;
private Types typeUtils;
private SourceVersion getSourceVersion;
private Locale getLocale;
private static final String RANDOM_SUFFIX = “$$BindView”;
private HashMap
private HashMap
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(TestSelfAnnotation.class);
for (final Element element : elementsAnnotatedWith)
if (element.getKind() == ElementKind.FIELD) {
TestSelfAnnotation annotation = element.getAnnotation(TestSelfAnnotation.class);
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
ClassName className = ClassName.get(enclosingElement);
if (entryKey.containsKey(className.simpleName())) {
HashMap stringStringHashMap = entryKey.get(className.simpleName());
stringStringHashMap.put(element.getSimpleName().toString(), annotation.value() + "");
// entryKey.put(element.getEnclosingElement().getSimpleName().toString(), stringStringHashMap);
} else {
HashMap
hashMap.put(element.getSimpleName().toString(),
annotation.value() + “”);
elementIndexMap.put(className.simpleName(),
element);
entryKey.put(className.simpleName(), hashMap);
} }
writeClazz(entryKey, elementIndexMap);
return false;
}
private void writeClazz(HashMap
for (Map.Entry
entryKey.entrySet()) {
String key = map.getKey();
HashMap
Element element = elementIndexMap.get(key);
TypeElement enclosingElement = (TypeElement)
element.getEnclosingElement();
ClassName className = ClassName.get(enclosingElement);
MethodSpec.Builder builder =
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(className, “activity”);
if (value.size() > 0) {
for (HashMap.Entry
CodeBlock of = CodeBlock.of(“$N.” + entry.getKey() + “=” +
“$N.” + “findViewById(”
entry.getValue() + “);n”, “activity”, “activity”);
}
}
TypeSpec build =
TypeSpec.classBuilder(getClazzName(element) + RANDOM_SUFFIX)
.addModifiers(Modifier.PUBLIC)
.addMethod(builder.build())
.build();
try {
JavaFile javaFile =
JavaFile.builder(getPackageName(element), build).build();
javaFile.writeTo(getFilers); } catch (IOException e) {
e.printStackTrace();
}
}
}
private String getClazzName(Element element) {
Element enclosingElement = element.getEnclosingElement();
return enclosingElement.getSimpleName().toString();
}
private String getPackageName(Element element) {
PackageElement packageOf = elementUtils.getPackageOf(element);
return packageOf.getQualifiedName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
getMessagers = processingEnvironment.getMessager();
getFilers = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
}
@Override
public Set getSupportedAnnotationTypes() {
Set setClazzName = new LinkedHashSet<>();
System.out.println(" " + TestSelfAnnotation.class.getCanonicalName());
setClazzName.add(TestSelfAnnotation.class.getName());
return setClazzName;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
自动生成如下文件:
文件:javax.annotation.processing. Processor。
第三步module依赖注解后,在apt文件下生成。
常见的几个API:
AbstractProcessor篇:
AbstractProcessor,是一个抽象类,该类实现了接口Processor。 抽象类AbstractProcessor以及接口Processor都是位于包 javax.annotation.processing中。该接口中定义的所有类、接口都是与实现注解处理器相关的。但JavaWeb开发中也很常用只是太过于底层,常用的lombok的实现逻辑就是基于注解处理器,SPI(服务提供接口)原理也可以使用注解处理器实现。
ButterKnife
原理:在编译过程中,获取到注解的内容,生成JAVA文件及代码,再通过反射机制调用JAVA文件里的方法。
简单来说就是:我自动帮你把findViewByld写了类似这样:
package com.apt.viewlinkiddemo;
Cclass MainActivity ViewLink {
public MainActivity Viewl ink(MainActivity activity) {
activity .textview = activily findViewByld(2131231 103);
activity .bt = activity findViewByld(2131230807);
AbstractProcessor类中各方法的用法:
一.Iterable extends Complet ion>:
getCoapletioos (Elenent element, AnnotatiooMirror annotation, ExecutableElement meniber, string userText)返回一个空的completion迭代。
二.Set:
1.getSupportedAnnotationTypes( )
如果processor类是使用SupportedAnnotationTypes注释的,则返回一个不可修改的集合,该集合具有与注释相同的字符串集。
2.getSupportedOptions ( )
如果processor类是使用SupportedOptions注释的,则返回-一个不可修改的集合,该集合具有与注释相同的字符串集。
三.SourceVersion
getSupportedsourceversion( ) 如果processor类是使用SupportedSourceversion 注释的,则返回注释中的原版本。
四.void
init (ProcessingEnvironment process ingEnv )
用处理环境初始化processor,方法是将processingEnv字段设置为processingEnv参数的值。
五.protected boolean
isInitialized( ) 如果此对象已被初始化,则返回true,否则返回false。
六.abstract boolean
process(Set annotations, RoundEnvironment > annotations, RoundEnvironment roundEnv )
处理先前round产生的类型元素上的注释类型集,并返回这些注释是否由此Processor 声明。
ButterKnife自定义
在Android中,ButterKnife是一个通过注解实现依赖注入的框架,所以这里也通过实现一个简单的ButterKnife框架,熟悉通过 AbstractProcessor实现注解处理器的过程。
首先,需要创建两个Java库butterknife-annotations和butterknife-compiler以及一个android库butterknife
butterknife-annotations,用于声明具体的注解的,如BindView、OnClick等)。主要供butterknife-compiler和butterknife库引用 butterknife-compiler,用于声明自定义AbstractProcessor,在编译时生成具体的Java文件。 butterknife,这个就是具体android项目中需要引用的库,主要实现具体依赖注入的功能。 butterknife是一个android库工程,该库的作用主要就是完成依赖注入。在该库中会依赖butterknife-annotations,最终在Android项目中我们只需要依赖这个工程即可。
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();
使用butterknife完成依赖注入
实例;需要在项目中也就是在app/build.gradle文件中添加库依赖并且声明APT工具,如下图:
测试:
在项目主工程中定义了MainActivity和TwoActivity,在这两个Activity中使用注解@BindView,如下图:
通过Android Studio的Rebuild Project,就可以看到在主app工程build/generated/source/apt/debug目录下就已经生成了两个新的Java文件 如图:
看看MainActivity_ViewBinding类中的具体内容,发现在该文件中为MainActivity_ViewBinding创建了一个构造方法,该构造方法中需要传入指定的activity,然后在该构造方法中就可以完成button和textView的初始化即findViewByid的过程。
1// Generated code from Butter Knife. Do not modify! 2.package com. . zhangke . simplifybutterknife;
4import android.widget . Button; 5import android .widget .TextView; 6public final class MainActivity. ViewBinding { 7public MainActivity. ViewBinding(MainActivity target) { 8target button . (Button) target findVi6eyId(231165219); 9target.textView . (TextView) trget.finviByI(213165305);
想完成依赖注入,只需要通过工具类ButterKnife的bind方法就可以了,这个在上面MainActivity的源码中可以看到。 其实以上整个流程就是开源项目ButterKnife的基本工作原理 。
到这里, 一个简单的自定义ButterKnife就完成了 。
总结一下,以上就是有关apt的一些技术点。关于更多Android开发技术点击:
https://shimo.im/docs/5rk9dyvKa4Fz1Kqx/ ,免费领取《免费Android技术丶面试题纲丶核心笔记资料》



