APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。
简单来说就是在编译期,通过注解生成.java文件。
运行时注解(@Retention(RetentionPolicy.RUNTIME)):之前讲得运行时注解,是在程序运行的时候,获取和解析注解的,并且在获取一些信息后通过反射达到效果。运行时注解的例子。
编译时注解(@Retention(RetentionPolicy.CLASS)):编译时注解在编译的时候需要用到代理模式,生成对应java文件,在程序运行的时候,使用该java文件,并调用方法来达到预期的效果。
下面来一步步实现APT:
1.创建注解类:
将注解类放到单独的一个子module中,如:apt_annotation
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface InjectView {
int value();
}
2.创建AbstractProcessor实现类:
一般将这些相关的类放在一个子module中管理,如:apt_processor
@AutoService(Processor.class)
public class InjectViewProcessor extends AbstractProcessor {
private Messager mMessager;
private Elements mElementUtils;
private Map<String, ClassGenerateProxy> mProxyMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
/*** 这里是将使用到的注解类加入到集合中 ***/
supportTypes.add(InjectView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
/*** 这里主要是生成需要的Java文件,因此这里是关键的地方 ***/
...
return true;
}
其中的init、 getSupportedAnnotationTypes、getSupportedSourceVersion三个方法的写法是固定的,关键的地方是process方法,这里需要用来创建Java类文件,可以使用比较好用的第三方库:JavaPoet。
那么AbstractProcessor实现类的方法什么时候执行呢?这个时候需要运用到SPI技术。注意到BindViewProcessor类上面有一个注解@AutoService(Processor.class),这个是google的第三方库,需要在gradle里面引用:
implementation 'com.google.auto.service:auto-service:1.0-rc2'
但是有时候这种方法不生效,原因我也不是很清楚。。。
因此最稳的方法就是自己手动创建相关文件夹:
其中javax.annotation.processing.Processor里面的内容是AbstractProcessor实现类:BindViewProcessor类的路径:
com.zy.apt_processor. InjectViewProcessor
3.build,生成需要的Java类:
app模块需要编译子模块,如:
implementation project(':apt_annotation') annotationProcessor project(':apt_processor')
build app模块,此时就会在build目录下生成对应的Java文件,如:
4.使用注解:
在Activity中使用注解:
@InjectView(R.id.tv) TextView mTextView; @InjectView(R.id.btn) Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BindViewTools.bind(this); ... }
此时虽然咱们已经生成了Java类,但是还没有使用,那么使用如下:
1.Android Studio会在Build里面生成相应的Java类,所以,可以直接在程序中调用该类方法,XXXViewBinding.bind(this);
2.也可以自己编写一个注解注入的工具类:
这里是一个简单例子,其中使用反射拿到这个类的class对象和Method对象,来执行 注解注入的方法“public void bind(Activity activity)”
public class InjectViewTools {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
那么,到这里咱们的APT的简单实践就完成了,这里是使用编译时注解来省略程序员写的findViewById,当然实际上是没有省去的,因为编译时注解只是在默默地帮你写了段findViewById的程序,咱们就只需要使用注解将View注入,就达到了解耦的目的。很多第三方都是这么做的,比如号称超级解耦的retrofit也是使用编译时注解。
此处补充一下AbstractProcessor实现类的方法体代码(本文的例子):
@AutoService(Processor.class)
public class InjectViewProcessor extends AbstractProcessor {
private Messager mMessager;
private Elements mElementUtils;
private Map<String, ClassGenerateProxy> mProxyMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(InjectView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mProxyMap.clear();
//得到所有的注解
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
//elements的信息保存到mProxyMap中
ClassGenerateProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
proxy = new ClassGenerateProxy(mElementUtils, classElement);
mProxyMap.put(fullClassName, proxy);
}
InjectView bindAnnotation = variableElement.getAnnotation(BindView.class);
int id = bindAnnotation.value();
proxy.putElement(id, variableElement);
}
//通过javapoet生成
for (String key : mProxyMap.keySet()) {
ClassGenerateProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
try {
// 生成文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
}
}
ClassCreatorProxy类:主要完成Java类文件的创建过程
public class ClassGenerateProxy {
private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
this.mTypeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mPackageName = packageName;
this.mBindingClassName = className + "_ViewBinding";
}
public void putElement(int id, VariableElement element) {
mVariableElementMap.put(id, element);
}
/**
* 创建Java代码
* javapoet
*
* @return
*/
public TypeSpec generateJavaCode2() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())
.build();
return bindingClass;
}
/**
* 加入Method
* javapoet
*/
private MethodSpec generateMethods2() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build();
}
public String getPackageName() {
return mPackageName;
}
}
注意:这里的mMessager.printMessage是日志打印,这里也可以使用System.out.print(),这些日志的打印信息可以在Build out中找到,也就是在Android Studio最左下角有个build栏,点击打开就可以看到了。有时候代码没有达到预期的效果,可以通过看日志找到原因。如图: