annotationProcessor 是 javac 的一个工具,全称为 APT(apt 工具,Annotation Processor Tool),它用来在编译时扫描和处理注解,获取注解和被注解对象的相关信息,然后根据注解自动生成 java 代码。
简单理解就是在编译时将 java 文件编译为 class 文件时,annotationProcessor 能通过标记的注解对源码编译过程做一些调整辅助生成一些代码,提高代码执行性能。
APT注解处理器
APT全称Annotation Processing Tool,即注解处理器,它在代码编译时扫描和处理注解,即对源代码文件进行检测找出其中的注解,然后使用注解进行额外的处理,比如生成处理注解逻辑的Java文件等。
ButterKnife、EventBus、ARouter、GreenDAO都使用了APT技术。在ARouter框架中,路由表的生成过程就使用了APT技术,路由表就是在运行时生成用于帮助填充WareHouse路由元信息的类。
APT的作用时间是编译时。Android中代码编译流程:Java—>class —> dex,代码最终生成dex文件打入到APK包里面。
①APT是在编译开始时就介入的,用来处理编译时注解。
②AOP(Aspect Oridnted Programming)是在编译完成后生成dex文件之前,通过直接修改.class文件的方式,来对代码进行修改或添加逻辑。常用在代码监控、代码修改、代码分析这些场景。
使用APT可以在编译时处理注解,有如下效果:
①可以达到减少重复代码手工编写的效果。如ButterKnife,可以直接使用注解来减少findviewbyid这些代码,只需要通过注解表示是哪个id就够了。
②获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。
注意:APT并不能对源文件进行修改,只能获取注解信息和被注解对象的信息,然后做一些自定义的处理。
宏观上理解,APT就是javac提供给开发者在编译时处理注解的一种技术;微观上,具体到实例中就是指继承自AbstractProcessor的实现类,即一个处理特定注解的处理器。
APT原理
在Java源码到class文件之间需要经过注解处理器的处理,注解处理器生成的代码也同样会经过这一过程,最终一起生成class文件。在Android中,class文件还会被打进Dex文件中,最后生成APK文件。
使用步骤
事实上它是javac的一个工具,命令行运行javac后便可以看到:
接下来我们就来实现一个apt的实例,类似于ButterKnife中@BindView注解,基本步骤如下:
1、定义要被处理的注解。
2、定义注解处理器(生成具体的类)。
3、调用处理器生成的代码
对应的,我们在工程中需要有这几个模块:
1、app。测试我们的功能
2、apt-annotation。一个Java library module,放置我们自定义注解
3、apt-processor。一个Java library module,注解处理器模块
4、apt-sdk。一个Android library module,通过反射调用apt-processor模块生成的方法,实现view的绑定。
工程目录如下:
1、在apt-annotation中自定义注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView { int value(); }
2、apt-processor中引入依赖,它需要依赖apt-annotation,同时还需要依赖auto-service第三方库,后面创建注解处理器的时候需要用到。
apt-processor/build.gradle文件中:
implementation project(':apt-annotation') implementation 'com.google.auto.service:auto-service:1.0-rc2'
3、在pat-processor中创建注解处理器:
处理器需要继承AbstractProcessor,注意该module是 java module,如果创建的是android module的话那么就会找不到AbstractProcessor
@AutoService(Processor.class) @SuppressWarnings("unused") public class BindViewProcessor extends AbstractProcessor { private Elements mElementUtils; private Map<String, ClassCreatorFactory> mClassCreatorFactoryMap = new HashMap<>(); @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); //拿到工具类 mElementUtils = processingEnvironment.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { //这个注解处理器是给哪个注解用的 HashSet<String> supportType = new LinkedHashSet<>(); supportType.add(BindView.class.getCanonicalName()); return supportType; } @Override public SourceVersion getSupportedSourceVersion() { //返回java版本 return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { mClassCreatorFactoryMap.clear(); //得到所有包含该注解的element集合 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); for (Element element : elements) { //转换为VariableElement,VariableElement为element的子类 VariableElement variableElement = (VariableElement) element; //可以获取类的信息的element,也是element的子类 TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); //获取包名加类名 String fullClassName = classElement.getQualifiedName().toString(); //保存到集合中 ClassCreatorFactory factory = mClassCreatorFactoryMap.get(fullClassName); if (factory == null) { factory = new ClassCreatorFactory(mElementUtils, classElement); mClassCreatorFactoryMap.put(fullClassName, factory); } BindView bindViewAnnotation = variableElement.getAnnotation(BindView.class); int id = bindViewAnnotation.value(); factory.putElement(id, variableElement); } //开始创建java类 for (String key : mClassCreatorFactoryMap.keySet()) { ClassCreatorFactory factory = mClassCreatorFactoryMap.get(key); try { JavaFileObject fileObject = processingEnv.getFiler().createSourceFile( factory.getClassFullName(), factory.getTypeElement()); Writer writer = fileObject.openWriter(); //写入java代码 writer.write(factory.generateJavaCode()); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } return true; } }
需要注意的是代码中不能有中文,否则编译不通过,我这里为了方便注释解释加上了中文。
ClassCreatorFactory的代码如下,这个类负责提供需要写入新的类的代码:
public class ClassCreatorFactory { private String mBindClassName; private String mPackageName; private TypeElement mTypeElement; private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>(); ClassCreatorFactory(Elements elementUtils, TypeElement classElement) { this.mTypeElement = classElement; //PackageElement是element的子类,可以拿到包信息 PackageElement packageElement = elementUtils.getPackageOf(mTypeElement); String packageName = packageElement.getQualifiedName().toString(); String className = mTypeElement.getSimpleName().toString(); this.mPackageName = packageName; //生成的类的名称 this.mBindClassName = className + "_ViewBinding"; } public void putElement(int id, VariableElement element) { mVariableElementMap.put(id, element); } public String generateJavaCode() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("/**\n" + " * Auto Created by apt\n" + "*/\n"); stringBuilder.append("package ").append(mPackageName).append(";\n"); stringBuilder.append('\n'); stringBuilder.append("public class ").append(mBindClassName); stringBuilder.append(" {\n"); generateBindViewMethods(stringBuilder); stringBuilder.append('\n'); stringBuilder.append("}\n"); return stringBuilder.toString(); } private void generateBindViewMethods(StringBuilder stringBuilder) { stringBuilder.append("\tpublic void bindView("); stringBuilder.append(mTypeElement.getQualifiedName()); stringBuilder.append(" owner) {\n"); for (int id : mVariableElementMap.keySet()) { VariableElement variableElement = mVariableElementMap.get(id); String viewName = variableElement.getSimpleName().toString(); String viewType = variableElement.asType().toString(); stringBuilder.append("\t\towner."); stringBuilder.append(viewName); stringBuilder.append(" = "); stringBuilder.append("("); stringBuilder.append(viewType); stringBuilder.append(")(((android.app.Activity)owner).findViewById( "); stringBuilder.append(id); stringBuilder.append("));\n"); } stringBuilder.append(" }\n"); } public String getClassFullName() { return mPackageName + "." + mBindClassName; } public TypeElement getTypeElement() { return mTypeElement; } }
先不谈apt-sdk模块,我们先来看看生成的代码是怎么样的。
在app的gradle中引入:
implementation project(‘:apt-annotation’) annotationProcessor project(‘:apt-processor’)
特别要注意的是apt-processor模块的依赖引进要用 annotationProcessor,否则编译报错
两个activity中:
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv) TextView textView; @BindView(R.id.tv_1) TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
public class Main2Activity extends AppCompatActivity { @BindView(R.id.tv_2) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); } }
rebuild一下便可以看到在这个目录下有我们生成的文件了。
gradle高版本出现编译后没出现文件的问题,无奈只好降低版本,我使用的版本是gradle 3.1.4 + gralde_wrap gradle-4.4-all.zip
点进入其中一个可以看到是这样的代码:
/** * Auto Created by apt */ package com.example.aptsample; public class MainActivity_ViewBinding { public void bindView(com.example.aptsample.MainActivity owner) { owner.tv = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165360)); owner.textView = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165359)); } }
所以我们只要调用bindView就能够找到该view了,这也是apt-sdk要做的事情。
4、在apt-sdk中创建类,反射调用生成的类中的方法
public class DataApi { public static void bindView(Activity activity) { Class clazz = activity.getClass(); try { Class<?> bindViewClass = Class.forName(clazz.getName() + "_ViewBinding"); Method method = bindViewClass.getMethod("bindView", activity.getClass()); method.invoke(bindViewClass.newInstance(),activity); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
5、app的gradle中引入apt-sdk,然后代码调用DataApi的方法
implementation project(':apt-annotation') annotationProcessor project(':apt-processor') implementation project(':apt-sdk')
app的MainActivity中实现
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv) TextView textView; @BindView(R.id.tv_1) TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DataApi.bindView(this); tv.setText("a"); } }
这样就大功告成了
有关Android开发中的apt技术就解析到这里,有关更多的Android开发技术可以参考《Android核心技术手册》点击可以查看详细类目。