一.APT技术
APT(Annotation Process Tool),注解处理器。用来在编译时扫描和处理注解,按照一定的规则,生成相应的java文件。
Android 目前比较流行的Dagger2, ButterKnife, EventBus3都采用了APT技术。
二.APT使用
1. AbstractProcesser类
Java语言自带的Override等注解,虚拟机会默认处理。
对于自定义注解,需要我们自己处理。java提供一个AbstractProcesser.java类,我们需要继承该类,实现自动的注解处理器,来处理自定义注解
public abstract class AbstractProcessor implements Processor {
protected ProcessingEnvironment processingEnv;
private boolean initialized = false;
/**
* 构造器
*/
protected AbstractProcessor() {}
/**
* 指定可以被处理器识别的选项
*
* 默认实现:
* 如果处理器类使用{@SupportedOptions}注解,则返回一个不可修改的字符串set集
* 合,包含注解设置的属性值。如果未对类进行注解,则返回空集合
*/
public Set<String> getSupportedOptions() {
SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
if (so == null)
return Collections.emptySet();
else
return arrayToSet(so.value());
}
/**
* 指定此处理器支持的注解类型
*
* 默认实现:
* 如果此处理器类使用了{@SupportedAnnotationTypes}注解,则返回一个不可修改的
* set字符串集合,包含注解设置的属性值。如果未对类进行注释,则返回空集合
*/
public Set<String> getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
if (sat == null) {
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedAnnotationTypes annotation " +
"found on " + this.getClass().getName() +
", returning an empty set.");
return Collections.emptySet();
}
else
return arrayToSet(sat.value());
}
/**
* 如果处理器类使用{@SupportedSourceVersion}注解,则返回注释中设置的版本值。
* 如果没有对类进行注解,则返回Java 6.0
*
* 指定此处理器支持的最新Java版本,通常返回SourceVersion.latestSupported()
*/
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion sv = null;
if (ssv == null) {
sv = SourceVersion.RELEASE_6;
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedSourceVersion annotation " +
"found on " + this.getClass().getName() +
", returning " + sv + ".");
} else
sv = ssv.value();
return sv;
}
/**
* 初始化方法 ,只能初始化一次,否则会抛出IllegalStateException异常
*
*一般在这里获取我们需要的工具类
* @param processingEnv 提供工具类Elements, Types和Filer
*/
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
this.processingEnv = processingEnv;
initialized = true;
}
/**
* 注解处理方法,可以在这里写扫描、评估和处理注解的代码,生成Java文件
*/
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);
/**
* Returns an empty iterable of completions.
*/
public Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText) {
return Collections.emptyList();
}
protected synchronized boolean isInitialized() {
return initialized;
}
private static Set<String> arrayToSet(String[] array) {
assert array != null;
Set<String> set = new HashSet<String>(array.length);
for (String s : array)
set.add(s);
return Collections.unmodifiableSet(set);
}
}
2. 自定义注解
Java注解
这里自定义一个BindView,应用场景类似于ButterKnife框架的findView功能,用来找到View对象
注解定义如下:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
3. 自定义注解处理器
这里需要注意需要创建Java library Module,apply plugin: ‘java-library’
不然找不到AbstractProcessor 类
依赖:
implementation project(':annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
- :annotation:自定义注解的Module
-
auto-service依赖库:
为什么引入auto-service
在使用注解处理器需要先声明,步骤:
1、需要在 processors 库的 main 目录下新建 resources 资源文件夹;
2、在 resources文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;)
引入auto-service库,使用@AutoService
注解声明就可以自动完成APT的声明步骤。 - javapoet square依赖库:提供的生成Java文件开源库 github地址 中文教程
自定义注解处理器
一般注解处理器逻辑:
1. 遍历得到源码中,需要解析的元素列表。
2. 判断元素是否可见和符合要求。
3. 组织数据结构得到输出类参数。
4. 输入生成java文件。
5. 错误处理。
自定义APT,需要了解一下Java Element
Processor处理过程中,会扫描全部Java源码,代码的每一个部分都是一个特定类型的Element,它们像是XML一层的层级机构。
自定义BindView注解处理器:BinderProcessor类
@AutoService(Processor.class)
public class BinderProcessor extends AbstractProcessor {
private Elements mElementUtils;
private HashMap<String, BinderClassCreator> mCreatorMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//processingEnvironment.getElementUtils(); 处理Element的工具类,用于获取程序的元素,例如包、类、方法。
//processingEnvironment.getTypeUtils(); 处理TypeMirror的工具类,用于取类信息
//processingEnvironment.getFiler(); 文件工具
//processingEnvironment.getMessager(); 错误处理工具
mElementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//大部分class而已getName、getCanonicalNam这两个方法没有什么不同的。
//但是对于array或内部类等就不一样了。
//getName返回的是[[Ljava.lang.String之类的表现形式,
//getCanonicalName返回的就是跟我们声明类似的形式。
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
//因为兼容的原因,特别是针对Android平台,建议使用重载getSupportedAnnotationTypes()方法替代默认使用注解实现
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//扫描整个工程 找出含有BindView注解的元素
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(BindView.class);
//遍历元素
for (Element element : elements) {
//BindView限定了只能属性使用,这里强转为VariableElement
VariableElement variableElement = (VariableElement) element;
//返回此元素直接封装(非严格意义上)的元素。
//类或接口被认为用于封装它直接声明的字段、方法、构造方法和成员类型
//这里就是获取封装属性元素的类元素
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
//获取简单类名
String fullClassName = classElement.getQualifiedName().toString();
BinderClassCreator creator = mCreatorMap.get(fullClassName);
if (creator == null) {
creator = new BinderClassCreator(mElementUtils.getPackageOf(classElement),
classElement);
mCreatorMap.put(fullClassName, creator);
}
//获取元素注解
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
//注解值
int id = bindAnnotation.value();
creator.putElement(id, variableElement);
}
for (String key : mCreatorMap.keySet()) {
BinderClassCreator binderClassCreator = mCreatorMap.get(key);
//通过javapoet构建生成Java类文件
JavaFile javaFile = JavaFile.builder(binderClassCreator.getPackageName(),
binderClassCreator.generateJavaCode()).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
BinderClassCreator类 用于生成Java类代码,具体代码如下:
public class BinderClassCreator {
public static final String ParamName = "view";
private TypeElement mTypeElement;
private String mPackageName;
private String mBinderClassName;
private Map<Integer, VariableElement> mVariableElements = new HashMap<>();
/**
* @param packageElement 包元素
* @param classElement 类元素
*/
public BinderClassCreator(PackageElement packageElement, TypeElement classElement) {
this.mTypeElement = classElement;
mPackageName = packageElement.getQualifiedName().toString();
mBinderClassName = classElement.getSimpleName().toString() + "_ViewBinding";
}
public void putElement(int id, VariableElement variableElement) {
mVariableElements.put(id, variableElement);
}
public TypeSpec generateJavaCode() {
return TypeSpec.classBuilder(mBinderClassName)
//public 修饰类
.addModifiers(Modifier.PUBLIC)
//添加类的方法
.addMethod(generateMethod())
//构建Java类
.build();
}
private MethodSpec generateMethod() {
//获取所有注解的类的类名
ClassName className = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
//构建方法--方法名
return MethodSpec.methodBuilder("bindView")
//public方法
.addModifiers(Modifier.PUBLIC)
//返回void
.returns(void.class)
//方法传参(参数全类名,参数名)
.addParameter(className, ParamName)
//方法代码
.addCode(generateMethodCode())
.build();
}
private String generateMethodCode() {
StringBuilder code = new StringBuilder();
for (int id : mVariableElements.keySet()) {
VariableElement variableElement = mVariableElements.get(id);
//使用注解的属性的名称
String name = variableElement.getSimpleName().toString();
//使用注解的属性的类型
String type = variableElement.asType().toString();
//view.name = (type)view.findViewById(id)
String findViewCode = ParamName + "." + name + "=(" + type + ")" + ParamName +
".findViewById(" + id + ");\n";
code.append(findViewCode);
}
return code.toString();
}
public String getPackageName() {
return mPackageName;
}
}
4. APT生成代码使用
注解处理器完成了,怎么使用呢?需要通过反射来调用注解处理器生成代码
通过上面的注解处理器可知道,注解生成的类包名和使用注解的类的包名一致,类名为使用注解的类名__ViewBinding
,方法名为bindView
,传参为使用注解的类对象。所以可以如下通过反射调用
BinderViewTools类
public class BinderViewTools {
public static void init(Activity activity) {
Class clazz = activity.getClass();
try {
Class<?> bindClass = Class.forName(clazz.getName() + "_ViewBinding");
Method bind = bindClass.getMethod("bindView", clazz);
bind.invoke(bindClass.newInstance(),activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. APP Module中使用注解
依赖:
-
gradle>=2.2
使用annotationProcessor替代android-apt依赖注解处理器并进行工作
(关于annotationProcessor替代android-apt:能够辅助 Android Studio 在项目的对应目录中存放注解处理器在编译期间生成的文件,且打包时不会包含处理器代码)
在Moudle的gradle中配置
annotationProcessor project(':apt_lib')
implementation project(':annotation')
-
gradle<2.2
在project的gradle和Moudle的gradle中分别做如下配置
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
apt project(':apt_lib')
}
在Activity使用注解
MainActivity类
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
TextView mTextView;
@BindView(R.id.imageView)
ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BinderViewTools.init(this);
mTextView.setVisibility(View.VISIBLE);
mImageView.setVisibility(View.VISIBLE);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="获取View成功"
android:visibility="gone"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/textView"
android:layout_centerInParent="true"
android:src="@mipmap/ic_launcher"
android:visibility="gone"/>
</RelativeLayout>
两个View在XML中默认隐藏,使用BindView注解,然后调用BinderViewTools.init(this);
方法调用注解处理器生成的代码来获取View,最后设置View显示
注解处理器生成代码 MainActivity_ViewBinding类
public class MainActivity_ViewBinding {
public void bindView(MainActivity view) {
view.mTextView=(android.widget.TextView)view.findViewById(2131165295);
view.mImageView=(android.widget.ImageView)view.findViewById(2131165240);
}
}
运行效果
可以看到注解处理器正常生成代码,并且最终通过反射成功调用,View对象获取成功。
参考文章
1.[Android] APT
2.Android注解快速入门和实用解析