什么是注解
注解,通俗的来说,就是像注释一样,是由程序员在代码中加入的一种“标注”,不影响所编写的原有代码的执行。而这种标注(注解)可以被编码用的IDE、编译器、类加载器的代理程序、其他第三方工具以及原有代码运行期间读取和处理,生成一些新的辅助代码或是提示,从而节省时间,提升效率。这些工具读取注解的时机是根据注解的生命周期来定的,注解的生命周期就是其“存在寿命”,分为三种:
1,源注解
@Retention(RetentionPolicy.SOURCE)
注解将被编译器丢弃。如:@Override
2,类注解(ButterKnife)
@Retention(RetentionPolicy.CLASS)
注解由编译器记录在类文件中,但不需要由VM在运行时保留。
3,运行时注解(EventBus)
@Retention(RetentionPolicy.RUNTIME)
注解由编译器记录在类文件中,并在运行时由VM保存,因此可以反射性地读取它们。 如:@Deprecated
APT(Annotation Processing Tool)注解处理器, 是一个Gradle插件,协助Android Studio 处理annotation processors,
是一种处理注解的工具,确切的说它是javac的一个工具,可以在代码编译期解析注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。
Android Gradle插件2.2版本发布后,Android 官方提供了annotationProcessor插件来代替android-apt,annotationProcessor同时支持 javac 和 jack 编译方式,而android-apt只支持 javac 方式。
同时android-apt作者宣布不在维护,当然目前android-apt仍然可以正常运行
总体流程:自定义注解->自定义注解处理器(会用到javapoet)->注册注解处理器(会用到auto-service)->编译生成java代码
这面我只是简单的做了findViewId和onCliclk事件!
那我们开始说起:
如图:
apt_annotation ,一个Java Library
主要是用来自定义注解
apt_library,一个Android Library
主要是用来写调用的编译时期生成的java代码的工具类
apt_processor ,一个Java Library
主要是用来处理编译时的注解操作
为什么要建立java Library呢 ?
原因AbstractProcessor不在Android SDK里面!要是不建立 Java Library 是调用不到的!在java jre中。
首先:在apt_annotation module 建立注解
//编译时期注解,作用目标 域生明(类,接口,成员变量,类静态变量)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
@Retention(RetentionPolicy.CLASS)
@Target(METHOD)
public @interface OnClick {
int[] value();
}
那注解写好了:
再来:apt_processor module 建立编译时注解处理的逻辑
在moudle中添加依赖
implementation project(':apt_annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.11.1'
@AutoService(Processor.class)
public class BindViewProcessorByPoet extends AbstractProcessor {
//写入代码会用到
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每个类,要生成的代码集合;
Map> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List codeList = builderMap.get(typeElement);
// 去生成对应的 类文件;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
private Map> findAndBuilderByTargets(RoundEnvironment env) {
Map> builderMap = new HashMap<>();
// 遍历带对应注解的元素,就是某个View对象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//感觉这里面应该是VariableElement
BindViewCreatorByPoetHelper.parseBindView(element, builderMap);
}
// 遍历带对应注解的元素,就是某个方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
BindViewCreatorByPoetHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
}
这面会实现4个方法:
init:初始化。可以得到ProcessingEnviroment,ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer
getSupportedAnnotationTypes:指定这个注解处理器是注册给哪个注解的,这里说明是注解BindView和OnClick
getSupportedSourceVersion:指定使用的Java版本,通常这里返回SourceVersion.latestSupported()
process:可以在这里写扫描、评估和处理注解的代码,生成Java文件
所以说主要的还是
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每个类,要生成的代码集合;
Map> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List codeList = builderMap.get(typeElement);
// 去生成对应的 类文件;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
这一方法:
我们详细看下
因为大都数代码里面都是有注释的:
private Map> findAndBuilderByTargets(RoundEnvironment env) {
Map> builderMap = new HashMap<>();
// 遍历带对应注解的元素,就是某个View对象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//感觉这里面应该是VariableElement
BindViewCreatorByPoetHelper.parseBindView(element, builderMap);
}
// 遍历带对应注解的元素,就是某个方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
BindViewCreatorByPoetHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
Map
这一个集合进行存储,key则是其实也就是关联Actvity对象的Element,value则是写入的代码集合(一个类维护一个生成的代码块的集合)
然后分别对两个注解添加代码集合:
public class BindViewCreatorByPoetHelper {
public static void parseBindView(Element element, Map> codeBuilderMap) {
//获取最外层的类名,具体实际就是关联某个Activity对象Element
//因为此时的element是VriableElement,所以拿到的Enclosing 就应该是Activity对象
TypeElement classElement = (TypeElement) element.getEnclosingElement();
// 这个view是哪个类 Class(android.widget.TextView)
String viewType = element.asType().toString();
// 注解的值,具体实际可能就是 R.id.xxx
int value = element.getAnnotation(BindView.class).value();
// 这个view对象名称(比如TextView)
String name = element.getSimpleName().toString();
//创建代码块
//$L是占位符,会把后面的 name 参数拼接到 $L 所在的地方
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", name);
builder.add("($L)target.findViewById($L)", viewType, value);
List codeList = codeBuilderMap.get(classElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(classElement, codeList);
}
codeList.add(builder);
}
public static void parseListenerView(Element element, Map> codeBuilderMap) {
//获取最外层的类名,具体实际就是关联某个Activity对象Element
TypeElement classElement = (TypeElement) element.getEnclosingElement();
List codeList = codeBuilderMap.get(classElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(classElement, codeList);
}
//注解的值
int[] annotationValue = element.getAnnotation(OnClick.class).value();
//因为注解@Target是Method,所以这面拿到的就是方法名字的字符串
String name = element.getSimpleName().toString();
//创建代码块
for (int value : annotationValue) {
CodeBlock.Builder builder = CodeBlock.builder();
builder.add("target.findViewById($L).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.$L(v); }})", value, name);
codeList.add(builder);
}
}
public static void writeBindView(TypeElement classElement, List codeList, Filer filer) {
// enclosingElement ,暗指 某个Activity.
// 先拿到 Activity 所在包名( cn.citytag.aptdemo.Main3Activity)
String packageName = classElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));//(cn.citytag.aptdemo)
// 再拿到Activity类名(Main3Activity))
String className = classElement.getSimpleName().toString();
//此元素定义的类型
TypeName type = TypeName.get(classElement.asType());
//if (type instanceof ParameterizedTypeName) {
// type = ((ParameterizedTypeName) type).rawType;
//}
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBindingPoet");
MethodSpec.Builder methodSpecBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(type, "target", Modifier.FINAL)
.addParameter(ClassName.get("android.view", "View"), "source", Modifier.FINAL);
for (CodeBlock.Builder codeBuilder : codeList) {
//方法里面 ,代码是什么
methodSpecBuilder.addStatement(codeBuilder.build());
}
methodSpecBuilder.build();
// 创建类 MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
//将文件写出
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
List codeList = codeBuilderMap.get(classElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(classElement, codeList);
}
都会加以判断是否存在此TypeElemen的key,在进行put元素!
这样的话代码集合添加完成之后再进行写入,
还是这个代码,每一个TypeElemen对应一个代码块集合进行写入代码;
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每个类,要生成的代码集合;
Map> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List codeList = builderMap.get(typeElement);
// 去生成对应的 类文件;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
public static void writeBindView(TypeElement classElement, List codeList, Filer filer) {
// classElement ,就是关联的某个Activity
// 先拿到 Activity 所在包名( cn.citytag.aptdemo.Main3Activity)
String packageName = classElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));//(cn.citytag.aptdemo)
// 再拿到Activity类名(Main3Activity))
String className = classElement.getSimpleName().toString();
//此元素定义的类型
TypeName type = TypeName.get(classElement.asType());
//if (type instanceof ParameterizedTypeName) {
// type = ((ParameterizedTypeName) type).rawType;
//}
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBindingPoet");
MethodSpec.Builder methodSpecBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(type, "target", Modifier.FINAL)
.addParameter(ClassName.get("android.view", "View"), "source", Modifier.FINAL);
for (CodeBlock.Builder codeBuilder : codeList) {
//方法里面 ,代码是什么
methodSpecBuilder.addStatement(codeBuilder.build());
}
methodSpecBuilder.build();
// 创建类 MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
//将文件写出
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
然后:apt_library module 建立Tools类
public class BindViewByPoetTools {
public static void bind(Activity activity) {
//获取activity的decorView(根view)
View view = activity.getWindow().getDecorView();
bind(activity, view);
}
private static void bind(Object obj, View view) {
String className = obj.getClass().getName();
//找到该activity对应的Bind类的名字
String generateClass = className + "_ViewBindingPoet";
//然后调用Bind类的构造方法,从而完成activity里view的初始化
try {
Class.forName(generateClass).getConstructor(obj.getClass(), View.class).newInstance(obj, view);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
最后:app module 进行绑定注解,并调用Tools类!
在app module添加依赖
implementation project(':apt_annotation')
implementation project(':apt_library')
annotationProcessor project(':apt_processor')
为什么没用apt呢!gradle高版本就不用那么麻烦了!直接annotationProcessor这个就可以在编译时处理注解了!
public class Main3Activity extends AppCompatActivity {
@BindView(R.id.tv_one)
TextView mTextViewOne;
@BindView(R.id.tv_two)
TextView mTextViewTwo;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
BindViewByPoetTools.bind(this);
mTextViewOne.setText("one");
mTextViewTwo.setText("two");
}
@OnClick({R.id.tv_one, R.id.tv_two})
public void onBtn1Click(View v) {
Toast.makeText(this, "", Toast.LENGTH_SHORT).show();
}
}
最终:ReBuild as 则会生成如下代码:
package cn.citytag.aptdemo;
import android.view.View;
public class Main3Activity_ViewBindingPoet {
public Main3Activity_ViewBindingPoet(final Main3Activity target, final View source) {
target.mTextViewOne = (android.widget.TextView)target.findViewById(2131165325);
target.mTextViewTwo = (android.widget.TextView)target.findViewById(2131165326);
target.findViewById(2131165325).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
target.findViewById(2131165326).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
}
}
介绍下依赖库auto-service:
auto-service的作用是向系统注册processor(自定义注解处理器),
在javac编译时,才会调用到我们这个自定义的注解处理器方法。
主要是自己建立我没有试!这个具体我也不清楚!
在使用注解处理器需要先声明,步骤:
1、需要在 processors 库的 main 目录下新建 resources 资源文件夹;
2、在 resources文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;
这样声明下来也太麻烦了?这就是用引入auto-service的原因。
通过auto-service中的@AutoService可以自动生成AutoService注解处理器是Google开发的,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的
介绍下依赖库 javapoet:
助于在编译期间生成java代码,要不自己StringBuilder拼接很麻烦!
https://github.com/square/javapoet
如果在as ReBuild的时候报这个问题:
错误: 编码GBK的不可映射字符
在apt_processor gradle
加入下面代码!
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}