前言
java学习时都应该学习过注解,但是一直不知道注解怎么用,怎么发挥他的效果。最近开始Android学习,然后开始看Butterknife源码学习。如果你也想学习butterknife的实现,那么可以看看这篇文章。
注解在我们android开发和java开发中有很多作用,今天我们就来介绍下他的一种高级用法:注解处理器(APT:Annotation Processing Tool)
注解基础知识
1、元注解:
1、@Target(xxx):目标,代表该注解可用在何处
参数类型有:ElementType.FIELD //注解用在属性上
ElementType.TYPE//注解用在类上
ElementType.CONSTRUCTOR //注解用在构造方法上
ElementType.METHOD //注解用在方法上
ElementType.ANNOTATION_TYPE //注解用在注解类型上
ElementType.LOCAL_VARIABLE //用在本地变量上
ElementType.PARAMETER //可以给一个方法内的参数进行注解
.......
还有很多,这里就不赘述了,常用的就那么几个:ElementType.FIELD、ElementType.METHOD、ElementType.TYPE
2、@Retention(RetentionPolicy.xx) :保持、保留,代表注解的作用域。
参数有:
RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME
RetentionPolicy.SOURCE:代表注解的作用只在源码层,编译后的自己码中注解会消失。
RetentionPolicy.CLASS:注解会在源码编译后的自己码中也存在,但是不会保持到VM的运行时。
RetentionPolicy.RUNTIME:注解会保持到VM运行时。
3、@Repeatable:定义注解可重复
4、@Inherit:定义子类继承父类的注解
5、@Document:将注解包含到javaDoc中
2、元注解的使用:
1、创建一个注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
2、使用注解
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_test)
TextView tvTest;
@BindView(R.id.btn_modify)
Button btnModify;
........此处省略其他代码...........
}
3、运行时注解:
主要是使用元注解标识:@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
4、编译时注解(这是我们要讲的重点,ButterKnife神器的实现原理主要也是它)
知识储备:
1、APT是一种注解解析工具:在编译期找出源代码中所有的注解信息,如果指定了注解器(继承AbstractProcessor),那么在编译期会调用这个注解器里面的代码,我们可以在这里面做一些处理, 如根据注解信息动态生成一些代码,并将代码注入到源码中。
2、使用到的工具:
工具类1 Element:表示程序的一个元素,它只在编译期存在。可以是package,class,interface,method,成员变量,函数参数,泛型类型等。
Element的子类:
- ExecutableElement:类或者接口中的方法,构造器或者初始化器等元素
- PackageElement:代表一个包元素程序
- VariableElement:代表一个类或者接口中的属性或者常量的枚举类型,方法或者构造器的参数,局部变量,资源变量或者异常参数
- TypeElement:代表一个类或者接口元素
- TypeParameterElement:代表接口,类或者方法的泛型参数元素
通过Element可以获取什么信息呢?
1.asType() 返回TypeMirror:
TypeMirror是元素的类型信息,包括包名,类(或方法,或参数)名/类型
TypeMirror的子类:ArrayType, DeclaredType, DisjunctiveType, ErrorType, ExecutableType, NoType, NullType, PrimitiveType, ReferenceType, TypeVariable, WildcardType
2.equals(Object obj) 比较两个Element利用equals方法。
3.getAnnotation(Class annotationType) 传入注解可以获取该元素上的所有注解。
4.getAnnotationMirrors() 获该元素上的注解类型。
5.getEnclosedElements() 获取该元素上的直接子元素,类似一个类中有VariableElement。
6.getEnclosingElement() 获取该元素的父元素,
如果是属性VariableElement,则其父元素为TypeElement,
如果是PackageElement则返回null,
如果是TypeElement则返回PackageElement,
如果是TypeParameterElement则返回泛型Element
7.getKind() 返回值为ElementKind,通过ElementKind可以知道是那种element,具体就是Element的那些子类。
8.getModifiers() 获取修饰该元素的访问修饰符,public,private
9.getSimpleName() 获取元素名,不带包名,
如果是变量,获取的就是变量名,
如果是定义了int age,获取到的name就是age。
工具类2 ProcessingEnvironment:
APT运行环境:里面提供了写新文件, 报告错误或者查找其他工具.
1.getFiler():返回用于创建新的源,类或辅助文件的文件管理器。
2.getElementUtils():返回对元素进行操作的一些实用方法的实现.
3.getMessager():返回用于报告错误,警告和其他通知的信使。
4.getOptions():返回传递给注解处理工具的处理器特定选项。
5.getTypeUtils():返回一些用于对类型进行操作的实用方法的实现。
工具类3 ElementKind:
如何判断Element的类型呢,需要用到ElementKind,ElementKind为元素的类型,元素的类型判断不需要用instanceof去判断,而应该通过getKind()去判断对应的类型
element.getKind()==ElementKind.CLASS;
工具类4 TypeKind:
TypeKind为类型的属性,类型的属性判断不需要用instanceof去判断,而应该通过getKind()去判断对应的属性
element.asType().getKind() == TypeKind.INT
基础知识了解后,下面我们开始使用自定义使用APT,通过模仿ButterKnife来进行练习
步骤1:创建一个java lib模块:annotations 注意,这个依赖model是java library
步骤2:再创建一个java library(annotation_compiler)
创建的这个java library,主要用于编译时,生成想要的代码。
这个类主要注意几点:
1、使用到了google的auto-service工具,需要在该模块build.gradle中添加。
2、模块工具类AnnotationCompiler(名字自己随意),需要使用注解@AutoService(Process.class)标识,这样才会想spring中被扫描到,然后需要extends AbstractProcessor(实现AbstractProcessor的抽象法)。
3、实现AbstractProcessor的抽象法:
//1、支持的版本:固定写支持最新版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//2、能用来处理哪些注解
@Override
public Set getSupportedAnnotationTypes() {
Set types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
//3.定义一个用来生成APT目录下面的文件的对象
Filer filer;
//4.初始化filer
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
//5、编译是在这里干坏事,把想要生成的代码在此方法中实现
//这里需要用来创建Java类文件,可以使用比较好用的第三方库:JavaPoet
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnv) {
}
那么AbstractProcessor实现类的方法什么时候执行呢?这个时候需要运用到SPI技术。注意到BindViewProcessor类上面有一个注解@AutoService(Processor.class),这个是google的第三方库,需要在gradle里面引用。
模块build.gradle源码:
plugins {
id 'java-library'
}
dependencies {
implementation fileTree(dir: 'libs', includes: ['*.jar'])
implementation project(path: ':annotations')
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
完整的AnntotationCompiler源码:
package com.xmaroon.annotation_compiler;
import com.google.auto.service.AutoService;
import com.xmaroon.annotations.BindView;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
* @author xuqingsong
* 注解处理程序,用来生成代码的
*/
@AutoService(Process.class)
public class AnnotationCompiler extends AbstractProcessor {
//1、支持的版本:固定写支持最新版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//2、能用来处理哪些注解
@Override
public Set getSupportedAnnotationTypes() {
Set types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
//3.定义一个用来生成APT目录下面的文件的对象
Filer filer;
//4.初始化filer
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
//5、编译是在这里干坏事,把想要生成的代码在此方法中实现
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnv) {
if (set.isEmpty()) {
return false;
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "xuqingsong---" + set);
Map> map = new HashMap<>();
//获取APP中所有用到了BindView注解的对象
//TypeElement 类
//ExecutableElement 方法
//VariableElement 属性
Set extends Element> elementsAnnotationWith = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elementsAnnotationWith) {
VariableElement variableElement = (VariableElement) element;
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
Class aClass = variableElement.getEnclosingElement().getClass();
List variableElementList = map.get(activityName);
if (variableElementList == null) {
variableElementList = new ArrayList<>();
map.put(activityName, variableElementList);
}
variableElementList.add(variableElement);
}
//开始生成文件
if (map.size() > 0) {
Writer writer = null;
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String activityName = iterator.next();
List variableElements = map.get(activityName);
//得到包名
TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
try {
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
// package com.example.dn_butterknife;
writer.write("package " + packageName + ";\n");
// import com.example.dn_butterknife.IBinder;
writer.write("import " + packageName + ".IBinder;\n");
// public class MainActivity_ViewBinding implements IBinder<
// com.example.dn_butterknife.MainActivity>{
writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
packageName + "." + activityName + ">{\n");
// public void bind(com.example.dn_butterknife.MainActivity target) {
writer.write(" @Override\n" +
" public void bind(" + packageName + "." + activityName + " target){\n");
//target.tvText=(android.widget.TextView)target.findViewById(2131165325);
for (VariableElement variableElement : variableElements) {
//得到名字
String variableName = variableElement.getSimpleName().toString();
//得到ID
int id = variableElement.getAnnotation(BindView.class).value();
//得到类型
TypeMirror typeMirror = variableElement.asType();
writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
}
writer.write("\n}}");
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
}
步骤3:在主项目app中,创建IBinder类和关联bind工具类。我们的主要目的是为了编译时生成view被绑定的代码,如图是整个项目最终生成的代码:
生成的xxxActivity_ViewBinding源码:
package com.xmaroon.myannotation;
import com.xmaroon.myannotation.IBinder;
public class MainActivity_ViewBinding implements IBinder{
@Override
public void bind(com.xmaroon.myannotation.MainActivity target){
target.tvTest=(android.widget.TextView)target.findViewById(2131231176);
target.btnModify=(android.widget.Button)target.findViewById(2131230823);
}}
主项目app的build.gradle源码中注意在依赖时添加上依赖库:
implementation project(path:':annotations')
annotationProcessor project(path:':annotation_compiler')
这里我们再捋捋:
1、创建注解类BindView,只能拿到view的id,通过BindView注解的value得到。
2、使用apt工具生成自定义代码,这段代码实现了findViewById,并且这段代码的需要传入的参数是对应的view的那个Activity。
3、为了实现bind,①定义接口IBinder类,②通过反射使用自定义生成的java代码xxxActivity_ViewBinding来调用bind。
IBinder源码:
public interface IBinder {
/**
* bind view
* @param activity
*/
void bind(T activity);
}
XButterKnife源码:
public class XButterKnife {
public static void bind(Activity activity) {
String name = activity.getClass().getName() + "_ViewBinding";
try {
Class> aClass = Class.forName(name);
IBinder iBinder = (IBinder) aClass.newInstance();
iBinder.bind(activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
在具体的Activity中调用:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_test)
TextView tvTest;
@BindView(R.id.btn_modify)
Button btnModify;
int count = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
XButterKnife.bind(this);
btnModify.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
StringBuilder value = new StringBuilder("修改了");
if (count != 0) {
value.append(count);
}
tvTest.setText(value);
count++;
}
});
}
}
这里主要是基础的使用apt实现生成代码。编译是发现会生产对应的代码,即步骤3中的xxxActivity_ViewBinding。
注意:我在编译是总是不能生成这个代码,因为我忽略了一个小细节,使用apt时,需要一个文件将AnnotationCompiler这个自定义类运行起来,但是这个类自己操作时却是需要自己创建的
如下图:
在annotation_compiler模块下,main下的java同级目录resources文件夹中,创建META-INF => services => javax.annotation.processing.Processor
javax.annotation.processing.Processor文件中的源码:
com.xmaroon.annotation_compiler.AnnotationCompiler
创建的自定义编译生成文件类,这个文件的作用应该是相当于注册,通过注册后,才能被扫描运行起来!!!
基本功能已实现,接下来继续升级!!关注我后续文章