什么是注解
注解(Annotation),也叫元数据(即描述数据的数据),一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
起初作用基本类似是加强型的注释语言,为了编译检查,增强可阅读性,后续也可以用来生成代码,起到辅助项目的作用。
基本注解
内置注解
java内置注解,举个🌰:
@Override:让编译器检查被标记的方法,保证其重写了父类的某一个方法。此注解只能标记方法。源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
元注解
用来给其他注解打标签的注解,即用来解释其他注解的注解,举两个🌰:
@Retention:用于指定被此元注解标注的注解的保留时长,即作用域
RetentionPolicy.SOURCE: :在源文件中有效(即源文件保留),例如@Override
RetentionPolicy.CLASS::注解信息保留在class文件中,但是虚拟机不会持有其信息,可用来编译期注解
RetentionPolicy.RUNTIME::注解信息保留在class文件中,而且虚拟机也会持有此注解信息,所以可以通过反射的方式获得注解信息,用来运行时注解
编译期注解: 在class文件中可用,能够存于编译之后的字节码之中,该注解的注册信息会保留在.java源码里和.class文件里,在执行的时候,会被Java虚拟机丢弃,不会加载到虚拟机中。
运行期注解:Java虚拟机在运行期保留注解信息,可以通过反射机制读取注解的信息(.java源码,.class文件和执行的时候都有注解的信息)
@Target:注解对象的作用范围
CONSTRUCTOR:用于描述构造器
FIELD:用于描述字段
LOCAL_VARIABLE:用于描述局部变量
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述参数
TYPE:用于描述类、接口(包括注解类型) 或enum声明
自定义注解
编译期注解总体结构
编译时注解 + APT (Annotation Processing Tool)+ JavaPoet(自定义Java源文件) + auto-service(处理器注册)
APT(Annotation Processing Tool) 是一种处理注释的工具, 它对源代码文件进行检测找出其中的 Annotation,使用 Annotation 进行额外的处理。处理器在处理 Annotation 时可以根据源文件中的 Annotation 生成额外的源文件和其它的文件 (文件具体内容由 Annotation 处理器的编写者决定),APT 还会编译生成的源文件和原来的源文件,将它们一起生成 class 文件。
JavaPoet是一款可以自动生成Java文件的第三方库,简洁易懂的API,让繁杂、重复的Java文件,自动化生成,简化流程。
auto-service:编译时注解处理器AbstractProcessor需要注册到JVM中。使用@Autoservice注解就完成了注册,替代以前的手动配置(🌰:在Java Library项目中,在resources资源文件夹下创建META-INF.services,然后在该路径下创建名为javax.annotation.processing.Processor的文件,在该文件中配置(处理器的完整路径,每行一个)需要启用的注解处理器)。
编译期注解处理流程
screenshot-20220904-203719.png编译期注解实践
写了ShadowAptDemo,实现类似butterknife @BindView方法,根据注解自动获取view对象
c7101025-1714-43cc-bf29-2d232875ccbd.pngapp:主项目
apt_knife_api:功能API,给主项目依赖调用,Android lib
apt_complier:注解处理器,AbstractProcessor是javax.annotation.processing包下的一个抽象类,Android平台是基于OpenJDK的,而OpenJDK中不包含AbstractProcessor的相关代码,所以是一个Java lib
apt_annotations:注解的定义,需要给到注解处理器以及app主项目使用,所以也是Java lib
JDK的版本要一致,不然编译出错
注解的定义
apt_annotations:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ShadowBindView {
int value();
}
定义一个编译期作用域的注解 ShadowBindView,用于描述字段
里面包含一个方法用于接收ResID
注解处理器
apt_complier:
需要配置相关三方库的依赖
dependencies{
//处理器注册
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation 'com.google.auto:auto-common:0.10'
//java文件生成
implementation 'com.squareup:javapoet:1.11.1'
//注解依赖
implementation project(':apt_annotations')
}
自定义注解处理器
进行相关变量的初始化
@AutoService(Processor.class) //使用auto进行注册注解处理器,只需要在这里声明即可
@SupportedSourceVersion(SourceVersion.RELEASE_7) //指定jdk版本
//声明需要处理的注解
@SupportedAnnotationTypes("com.example.apt_annotations.ShadowBindView")
public class ShadowProcessor extends AbstractProcessor {
//Processor工具元素
private Elements processorElements;
//生成文件
private Filer filer;
//自定义注解类集合
private Map<String, AnnotationClass> mAnnotatedClassMap;
//注解处理器初始化的时候回调
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//返回包含用于操作Element的工具方法元素
processorElements = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
mAnnotatedClassMap = new HashMap<>();
}
....
AbstractProcessor是抽象类,有一个必须实现的抽象方法process方法,是注解处理器处理注解时候进行回调,需要在这里进行扫描和处理注解。
返回值逻辑
true,则这些注解已声明,且不要求后续Processor处理它们
false,则这些注解未声明,且可能要求后续Processor处理它们
RoundEnvironment,是指这轮处理注解所需要的元素信息,常用方法有:
getElementsAnnotatedWith,返回包含指定注解类型的元素的集合
errorRaised,上一轮注解处理器是否产生错误
Element,基础元素, 以下是继承它的元素
VariableElement ,代表成员变量
ExecutableElement ,代表类中的方法
TypeElement ,代表类
PackageElement ,代表Package
ShadowProcessor
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("process: " + roundEnv.toString());
mAnnotatedClassMap.clear();
//创建自定义注解处理类
try {
processBindView(roundEnv);
} catch (Exception e) {
e.printStackTrace();
}
//将自定义注解处理类,写入文件
for (AnnotationClass annotationClass : mAnnotatedClassMap.values()) {
try {
annotationClass.generateFiler().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private void processBindView(RoundEnvironment roundEnv) {
//遍历包含ShadowBindView注解类型的元素的集合
for (Element element : roundEnv.getElementsAnnotatedWith(ShadowBindView.class)) {
//注解类,AnnotationClass后续会详细讲到
AnnotationClass annotationClass = createAnnotationClass(element);
//构建注解变量类,后续会详细讲到
BindViewField bindViewField = new BindViewField(element);
//添加对应的变量
annotationClass.addField(bindViewField);
System.out.println("processBindView annotatedClass: " + annotationClass);
System.out.println("processBindView bindViewField: " + bindViewField);
}
}
//组建注解类
private AnnotationClass createAnnotationClass(Element element) {
//获取包含ShadowBindView注解的类元素
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
String fullName = typeElement.getQualifiedName().toString();
System.out.println("createAnnotationClass typeElement: " + typeElement);
AnnotationClass annotationClass = mAnnotatedClassMap.get(fullName);
//集合中不存在,才添加到集合中,去重
if (annotationClass == null) {
//创建注解类 将类元素typeElement,工具元素processorElements传入
annotationClass = new AnnotationClass(typeElement, processorElements);
mAnnotatedClassMap.put(fullName, annotationClass);
}
return annotationClass;
}
创建注解变量类,用来存储注解变量数据
public class BindViewField {
//变量元素
private VariableElement mVariableElement;
private int mResId;
public BindViewField(Element element) {
//判断类型,是否是变量元素
if (element.getKind() != ElementKind.FIELD) {
return;
}
mVariableElement = (VariableElement) element;
//获取自定义注解类型的变量
ShadowBindView bindView = mVariableElement.getAnnotation(ShadowBindView.class);
//获取view对象Rid
mResId = bindView.value();
}
public int getResId() {
return mResId;
}
public Name getFieldName() {
return mVariableElement.getSimpleName();
}
//变量对应的类型
public TypeMirror getFieldType() {
return mVariableElement.asType();
}
}
在注解类之前,先创建一个接口IViewBinder,它是用来衔接api和生成类文件的通信
就定义了两个需要实际使用的方法,后面构建类的时候会实现这个接口
apt_knife_api
public interface IViewBinder<T> {
void bindView(T host);
void unbindView(T host);
}
创建注解类,用来存储类信息和构建类文件
public class AnnotationClass {
static final ClassNameINTERFACE = ClassName.get("com.example.apt_knife_api", "IViewBinder");
//注解类的BindView变量集合
private final ArrayList<BindViewField> mFields;
//注解类的元素
private final TypeElement mTypeElement;
//Processor元素
private final Elements mProcessorElements;
public AnnotationClass(TypeElement typeElement, Elements elements) {
mFields = new ArrayList<>();
mTypeElement = typeElement;
mProcessorElements = elements;
}
/**
* 添加BindView变量
*
* @param field
*/
public void addField(BindViewField field) {
mFields.add(field);
}
/**
* 利用javaPoet生成对应的.java代码
*
* @return
*/
public JavaFile generateFiler() {
//生成java方法bindView
MethodSpec.Builder bindViewBuidler = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC)//public
.addAnnotation(Override.class)//接口的复写方法
.addParameter(TypeName.get(mTypeElement.asType()), "host");//参数
//添加bindView方法的处理解析
for (BindViewField field : mFields) {
//方法参数
bindViewBuidler.addStatement("host.$N = ($T)(host.findViewById($L))"
, field.getFieldName()
, ClassName.get(field.getFieldType())
, field.getResId());
}
//生成java方法unbindView:
MethodSpec.Builder unbindViewBuilder = MethodSpec.methodBuilder("unbindView")
.addModifiers(Modifier.PUBLIC)//public
.addAnnotation(Override.class)//接口的复写方法
.addParameter(TypeName.get(mTypeElement.asType()), "host");//添加参数
//添加unbindView方法的处理解析
for (BindViewField field : mFields) {
unbindViewBuilder.addStatement("host.$N = null", field.getFieldName());
}
//生成java的类文件(.java的文件),
TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$ShadowViewBinder")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(INTERFACE,
TypeName.get(mTypeElement.asType())))//类实现的接口名IViewBinder
.addMethod(bindViewBuidler.build())//添加bindView方法
.addMethod(unbindViewBuilder.build())//添加unbindView方法
.build();
//添加包名 使用传入的工具类元素获取包名
String packageName = mProcessorElements.getPackageOf(mTypeElement).getQualifiedName().toString();
//生成JavaFile 然后会用JavaFile.writeTo(filer)写入文件
return JavaFile.builder(packageName, injectClass).build();
}
}
主要有两个方法,一个添加构建好的变量类,用于生成类文件时候生成变量,最后一个是使用javaPoet生成类文件
使用javaPoet生成类文件的关键步骤注释都有,有个特别值得关注的点是构建方法代码的时候
//添加方法代码
bindViewBuidler.addStatement("host.$N = ($T)(host.findViewById($L))", field.getFieldName() ,
ClassName.get(field.getFieldType()) , field.getResId());
可以看见在写配置代码的时候有多个不同的通配符,分别代表不同的含义,可以简单看一下源码
private void addArgument(String format, char c, Object arg) {
switch (c) {
//$N表示获取参数的name
case 'N':
this.args.add(argToName(arg));
break;
//$L表示字面意义,原样输出
case 'L':
this.args.add(argToLiteral(arg));
break;
//$S表示转成字符串
case 'S':
this.args.add(argToString(arg));
break;
//$T表示转成类型,并自动import
case 'T':
this.args.add(argToType(arg));
break;
default:
throw new IllegalArgumentException(
String.format("invalid format string: '%s'", format));
}
}
private String argToName(Object o) {
if (o instanceof CharSequence) return o.toString();
if (o instanceof ParameterSpec) return ((ParameterSpec) o).name;
if (o instanceof FieldSpec) return ((FieldSpec) o).name;
if (o instanceof MethodSpec) return ((MethodSpec) o).name;
if (o instanceof TypeSpec) return ((TypeSpec) o).name;
throw new IllegalArgumentException("expected name but was " + o);
}
调用API
apt_knife_api:
创建主项目可以调用的api类ShadowKnife
public class ShadowKnife {
//缓存类,避免重复反射创建
private static final MapbinderMap = new LinkedHashMap<>();
/**
* 注解绑定
*
* @param host 表示注解 View 变量所在的类,也就是注解类 进行绑定的目标对象
*/
public static void bind(Object host) {
String className = host.getClass().getName();
try {
//看下对应的ViewBinder是否存在
IViewBinder binder =binderMap.get(className);
if (binder == null) {
//不存在则通过 反射 创建一个 然后存入缓存 这个类是通过javapoet生成的
Class aClass = Class.forName(className + "$ShadowViewBinder");
binder = (IViewBinder) aClass.newInstance();
binderMap.put(className, binder);
}
//使用注解类的进行绑定
if (binder != null) {
binder.bindView(host);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* 解除注解绑定
*
* @param host
*/
public static void unBind(Object host) {
String className = host.getClass().getName();
IViewBinder binder =binderMap.get(className);
if (binder != null) {
binder.unbindView(host);
}
binderMap.remove(className);
}
}
主项目使用
先进行模块依赖
dependencies{
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
//注解处理器依赖
annotationProcessor project(':apt_complier')
implementation project(':apt_annotations')
implementation project(':apt_knife_api')
}
测试api代码
public class MainActivity extends AppCompatActivity {
@ShadowBindView(R.id.text1)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ShadowKnife.bind(this);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "test", Toast.LENGTH_LONG).show();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
ShadowKnife.unBind(this);
}
}
然后进行clean,编译,看一下是否生成预期所想的代码
编译过程打印的日志
7bd686bd-0a44-4358-bab9-1cf8a6d14078.png查看所生成的代码
695aaaba-22db-47ef-9c33-c05ced0e732f.png大功告成!
网友评论