1,背景
对于注解 的使用,想必大家都不陌生,它出现在我们的源码中,以及大部分框架中,比如ButterKnife
、Arouter
、Retrofit
,但它们是有区别的,其中前2个是编译时注解,最后一个是运行时注解。我们都知道用注解可以简化代码,但大多数同学对其中的原理和实现方式还是很陌生的,下面就带大家入门编译时注解。
2,结合实例讲解
以仿写ButterKnife
为例,内容如下:
- 创建自定义注解
@interface
; - 创建并注册注解处理器
AbstractProcesso
,生成处理注解逻辑的.java
文件; - 封装一个供外部调用的API,用的是
反射技术
,具体来说就是调用第二步中生成的代码中的方法; - 在项目中使用,比如
Activity
、Fragment
、Adapter

APT实现方案
这里按功能职责,分多个module来处理,具体如下:
annotation
:自定义注解(java lib)processor
:注解处理器AbstractProcessor (java lib)inject_api
: 供外部调用的API (android lib)app
:项目使用 android lib)
步骤一:创建自定义注解
创建一个Java Library,名称为annotation
,作用是保存所有注解。

步骤二:实现注解处理器AbstractProcessor
创建一个Java Library,名称为processor
,作用是扫描、解析、处理注解。
processor
的Gradle配置如下:

创建自己的Processor来实现注解处理器:
//通过 AutoService 将 Processor 声明到 META-INF 中
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
/**
* 生成文件的工具类
*/
private Filer filer;
/**
* 打印信息
*/
private Messager messager;
/**
* 元素相关
*/
private Elements elementUtils;
private Types typeUtils;
/**
* 存放被注解标记的所有变量,按类来划分
*/
private Map<String, ProxyInfo> proxyInfoMap = new HashMap<>();
/**
* 一些初始化操作,获取一些有用的系统工具类
*
* @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
elementUtils = processingEnv.getElementUtils();
typeUtils = processingEnv.getTypeUtils();
}
/**
* 设置支持的版本
*
* @return 这里用最新的就好
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 设置支持的注解类型
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
//添加支持的注解
HashSet<String> set = new HashSet<>();
set.add(BindView.class.getCanonicalName());
return set;
}
/**
* 注解内部逻辑的实现
* <p>
* Element代表程序的一个元素,可以是package, class, interface, method.只在编译期存在
* TypeElement:变量;TypeElement:类或者接口
*
* @param annotations
* @param roundEnv
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "annotations size--->" + annotations.size());
//1、获取要处理的注解的元素的集合
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
//process()方法会调用3次,只有第一次有效,第2,3次调用的话生成.java文件会发生异常
if (elements == null || elements.size() < 1) {
return true;
}
//2、按类来划分注解元素,因为每个使用注解的类都会生成相应的代理类
for (Element element : elements) {
checkAnnotationValid(element, BindView.class);
//获取被注解的成员变量
//这里被注解的类型只能是变量,所以可以直接强转
VariableElement variableElement = (VariableElement) element;
//获取该元素的父元素,这里是父类
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
//获取全类名
String className = typeElement.getQualifiedName().toString();
//获取被注解元素的包名
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//获取注解的参数
int resourceId = element.getAnnotation(BindView.class).value();
//生成ProxyInfo对象
//一个类里面的注解都在一个ProxyInfo中处理
ProxyInfo proxyInfo = proxyInfoMap.get(className);
if (proxyInfo == null) {
proxyInfo = new ProxyInfo(typeElement, packageName);
proxyInfoMap.put(className, proxyInfo);
}
proxyInfo.viewVariableElement.put(resourceId, variableElement);
}
//3、生成注解逻辑处理类
for (String key : proxyInfoMap.keySet()) {
ProxyInfo proxyInfo = proxyInfoMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.packageName, proxyInfo.generateProxyClass())
//在文件头部添加注释
.addFileComment("auto generateProxyClass code,can not modify")
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 检查注解是否可用
*
* @param annotatedElement
* @param clazz
* @return
*/
private boolean checkAnnotationValid(Element annotatedElement, Class clazz) {
if (annotatedElement.getKind() != ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.NOTE, "%s must be declared on field.", annotatedElement);
return false;
}
if (annotatedElement.getModifiers().contains(PRIVATE)) {
messager.printMessage(Diagnostic.Kind.NOTE, "%s() can not be private.", annotatedElement);
return false;
}
if (!isView(annotatedElement.asType())) {
return false;
}
return true;
}
//递归判断android.view.View是不是其父类
private boolean isView(TypeMirror type) {
List<? extends TypeMirror> supers = typeUtils.directSupertypes(type);
if (supers.size() == 0) {
return false;
}
for (TypeMirror superType : supers) {
if (superType.toString().equals("android.view.View") || isView(superType)) {
return true;
}
}
return false;
}
}
下面开始这个Processor类的详解:
-
继承
AbstractProcessor
,需要重写process()
方法,这里是处理注解内部逻辑的,也是本文的关键点之一; -
此外还需要实现几个简单的方法
init ()
、getSupportedSourceVersion()
、getSupportedAnnotationTypes()
-
getSupportedSourceVersion()
:设置支持的版本,一般用最新的就好; -
getSupportedAnnotationTypes()
:添加支持的注解类型,可以是单个/多个,用Set存储; -
init ()
:一些初始化操作,获取一些有用的系统工具类,比如生成文件、打印信息、处理元素等;
@AutoService(Processor.class)
,生成 META-INF 信息;这里使用 Google 的 AutoService 来创建 META-INF,并将被注解的文件声明到 META-INF 中,这里是ButterKnifeProcessor
。
下面是META-INF 的文件目录:

文件内容com.zx.processor.ButterKnifeProcessor,这就是我们自定义的注解处理器ButterKnifeProcessor
的路径。
接下来讲解最关键的process()
方法
它的工作内容是扫描处理注解,最终生成.java
文件,这个文件里面就是注解的业务逻辑。
这里主要分为3步:
-
1、通过
getElementsAnnotatedWith()
获取要处理的注解的元素的集合,换句话说,找到所有Class中被@BindView
注解标记的变量; -
2、遍历第一步中的元素集合,由于这个注解可能会在多个类中使用,所以我们以类名为单元划分注解 。具体说,新建一个
ProxyInfo
对象去保存一个类里面的所有被注解的元素;用proxyInfoMap
去保存所有的ProxyInfo
;大概是这个样子Map<String, ProxyInfo> proxyInfoMap = new HashMap<>();
-
3、在
ProxyInfo
中为每个使用了@BindView
注解的类生成一个代理类; -
4、遍历
proxyInfoMap
,通过ProxyInfo
和JavaFile
生成具体的代理类文件/**
* 注解内部逻辑的实现
*
* Element代表程序的一个元素,可以是package, class, interface, method.只在编译期存在
* TypeElement:变量;TypeElement:类或者接口
*
* @param annotations
* @param roundEnv
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "annotations size--->" + annotations.size());
//1、获取要处理的注解的元素的集合
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);//process()方法会调用3次,只有第一次有效,第2,3次调用的话生成.java文件会发生异常 if (elements == null || elements.size() < 1) { return true; } //2、按类来划分注解元素,因为每个使用注解的类都会生成相应的代理类 for (Element element : elements) { checkAnnotationValid(element, BindView.class); //获取被注解的成员变量 //这里被注解的类型只能是变量,所以可以直接强转 VariableElement variableElement = (VariableElement) element; //获取该元素的父元素,这里是父类 TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement(); //获取全类名 String className = typeElement.getQualifiedName().toString(); //获取被注解元素的包名 String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); //获取注解的参数 int resourceId = element.getAnnotation(BindView.class).value(); //生成ProxyInfo对象 //一个类里面的注解都在一个ProxyInfo中处理 ProxyInfo proxyInfo = proxyInfoMap.get(className); if (proxyInfo == null) { proxyInfo = new ProxyInfo(typeElement, packageName); proxyInfoMap.put(className, proxyInfo); } proxyInfo.viewVariableElement.put(resourceId, variableElement); } //3、生成注解逻辑处理类 for (String key : proxyInfoMap.keySet()) { ProxyInfo proxyInfo = proxyInfoMap.get(key); JavaFile javaFile = JavaFile.builder(proxyInfo.packageName, proxyInfo.generateProxyClass()) .addFileComment("auto generateProxyClass code,can not modify") .build(); try { javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } return true; }
具体来说,可以按如下分类:
PackageElement
一般代表PackageTypeElement
一般代表代表类VariableElement
一般代表成员变量ExecutableElement
一般代表类中的方法
通过javapoet生成注解逻辑代码的具体步骤如下:
/**
* 通过javapoet API生成代理类
* @return
*/
public TypeSpec generateProxyClass() {
//代理类实现的接口
ClassName viewInjector = ClassName.get("com.zx.inject_api", "IViewInjector");
//原始的注解类
ClassName className = ClassName.get(typeElement);
// 泛型接口,implements IViewInjector<MainActivity>
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(viewInjector, className);
//生成接口的实现方法inject()
MethodSpec.Builder bindBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class) //添加方法注解
.addParameter(className, "target")
.addParameter(Object.class, "source");
for (int id : viewVariableElement.keySet()) {
VariableElement element = viewVariableElement.get(id);
String fieldName = element.getSimpleName().toString();
bindBuilder.addStatement(" if (source instanceof android.app.Activity){target.$L = ((android.app.Activity) source).findViewById( $L);}" +
"else{target.$L = ((android.view.View)source).findViewById($L);}", fieldName, id, fieldName, id);
}
MethodSpec bindMethodSpec = bindBuilder.build();
//创建类
TypeSpec typeSpec = TypeSpec.classBuilder(proxyClassName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(parameterizedTypeName) //实现接口
.addMethod(bindMethodSpec)
.build();
return typeSpec;
}
这里通过javapoet的API去生成代理类的代码,大概就是TypeSpec
生成类,MethodSpec
生成方法。这里以MainActivity
举例,具体生成处理注解的代理类如下:
package com.zx.compileannotation;
import com.zx.inject_api.IViewInjector;
import java.lang.Object;
import java.lang.Override;
public class MainActivity_ViewBinding implements IViewInjector<MainActivity> {
@Override
public void inject(MainActivity target, Object source) {
if (source instanceof android.app.Activity) {
target.textView2 = ((android.app.Activity) source).findViewById(2131230920);
} else {
target.textView2 = ((android.view.View) source).findViewById(2131230920);
}
;
if (source instanceof android.app.Activity) {
target.recyclerView = ((android.app.Activity) source).findViewById(2131230849);
} else {
target.recyclerView = ((android.view.View) source).findViewById(2131230849);
}
;
if (source instanceof android.app.Activity) {
target.textView = ((android.app.Activity) source).findViewById(2131230919);
} else {
target.textView = ((android.view.View) source).findViewById(2131230919);
}
;
}
}
注意:这里只是以MainActivity
的代理类,实际上每个用到注解的类都会生成一个类似的代理类。
到这一步就会为每一个使用注解的类生成对应的代理类,生成的代理类路径如下,

步骤三:封装一个供外部调用的API
我们知道在processor
中已经生成了处理注解逻辑的代理类,那接下来就是调用了。首先我们要知道代理类是在编译器动态生成的,而且会有多个,所以我们只能通过反射找到这个类,然后调用它的方法
/**
* 根据使用注解的类和约定的命名规则,通过反射找到动态生成的代理类(处理注解逻辑)
* @param object 调用类对象
*/
private static IViewInjector findProxyActivity(Object object) {
String proxyClassName = object.getClass().getName() + PROXY;
Log.e(TAG, "findProxyActivity: "+proxyClassName);
Class<?> proxyClass = null;
try {
proxyClass = Class.forName(proxyClassName);
// Constructor<?> constructor = proxyClass.getConstructor(object.getClass());
return (IViewInjector) proxyClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
我们都知道在使用findViewById()
的时候分为2种情况,在Activity中可以直接使用;而在Fragment、Adapter中则是view. findViewById()
,所以在下面的接口方法中多设计了一个Object参数,如果是Activity传入它本身;如果是Fragment、Adapter则传入View,
public interface IViewInjector<T> {
/**
* 通过source.findViewById()
*
* @param target 泛型参数,调用类 activity、fragment等
* @param source Activity、View
*/
void inject(T target, Object source);
}
最后提供2个供外部activity、fragment等调用的方法,
/**
* Activity调用
*/
public static void bind(Activity activity) {
findProxyActivity(activity).inject(activity, activity);
}
/**
* Fragment、Adapter调用
*
* @param object
* @param view
*/
public static void bind(Object object, View view) {
findProxyActivity(object).inject(object, view);
}
而运行时的入口为,
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv1)
TextView textView;
@BindView(R.id.tv2)
TextView textView2;
@BindView(R.id.rv)
RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//运行时入口
ButterKnife.bind(this);
textView2.setText("this is Activity compile annotation");
addFragment();
initRv();
}
而ButterKnife.java的代码如下,
package com.zx.inject_api;
import android.app.Activity;
import android.util.Log;
import android.view.View;
/**
* @author 周旭
* @company 伊柯夫
* @e-mail 374952705@qq.com
* @time 2019/11/18
* @descripe
*/
public class ButterKnife {
private static final String TAG = "ButterKnife";
public static final String PROXY = "_ViewBinding";
/**
* Activity调用
*/
public static void bind(Activity activity) {
findProxyActivity(activity).inject(activity, activity);
}
/**
* Fragment、Adapter调用
*
* @param object
* @param view
*/
public static void bind(Object object, View view) {
//先找到对应的类,再调用对应的接口,和我们平时写代码无异,只是类时编译时生成的
findProxyActivity(object).inject(object, view);
}
/**
* 根据使用注解的类和约定的命名规则,通过反射找到动态生成的代理类(处理注解逻辑)
*
* @param object 调用类对象
*/
private static IViewInjector findProxyActivity(Object object) {
String proxyClassName = object.getClass().getName() + PROXY;
Log.e(TAG, "findProxyActivity: " + proxyClassName);
Class<?> proxyClass = null;
try {
proxyClass = Class.forName(proxyClassName);
// Constructor<?> constructor = proxyClass.getConstructor(object.getClass());
return (IViewInjector) proxyClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
通过上面注解,调用到注解生成的文件MainActivity_ViewBinding.java中,内容如下,
// auto generateProxyClass code,can not modify
package com.zx.compileannotation;
import com.zx.inject_api.IViewInjector;
import java.lang.Object;
import java.lang.Override;
public class MainActivity_ViewBinding implements IViewInjector<MainActivity> {
@Override
public void inject(MainActivity target, Object source) {
if (source instanceof android.app.Activity){target.textView2 = ((android.app.Activity) source).findViewById( 2131230920);}else{target.textView2 = ((android.view.View)source).findViewById(2131230920);};
if (source instanceof android.app.Activity){target.recyclerView = ((android.app.Activity) source).findViewById( 2131230849);}else{target.recyclerView = ((android.view.View)source).findViewById(2131230849);};
if (source instanceof android.app.Activity){target.textView = ((android.app.Activity) source).findViewById( 2131230919);}else{target.textView = ((android.view.View)source).findViewById(2131230919);};
}
}
代码就是刚process()写进入的,
/**
* 通过javapoet API生成代理类
* @return
*/
public TypeSpec generateProxyClass() {
//代理类实现的接口
ClassName viewInjector = ClassName.get("com.zx.inject_api", "IViewInjector");
//类
// ClassName className = ClassName.bestGuess(simpleClassName);
ClassName className = ClassName.get(typeElement);
// 类型变量
// TypeVariableName tTypeVariable = TypeVariableName.get("T");
// 泛型接口,implements IViewInjector<MainActivity>
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(viewInjector, className);
//生成构造方法
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(className, "target")
.addStatement("this.target = target");
//生成接口的实现方法inject()
MethodSpec.Builder bindBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class) //添加方法注解
.addParameter(className, "target")
.addParameter(Object.class, "source");
for (int id : viewVariableElement.keySet()) {
VariableElement element = viewVariableElement.get(id);
String fieldName = element.getSimpleName().toString();
bindBuilder.addStatement(" if (source instanceof android.app.Activity){target.$L = ((android.app.Activity) source).findViewById( $L);}" +
"else{target.$L = ((android.view.View)source).findViewById($L);}", fieldName, id, fieldName, id);
}
MethodSpec bindMethodSpec = bindBuilder.build();
// MethodSpec constructorMethodSpec = constructorBuilder.build();
//创建类
TypeSpec typeSpec = TypeSpec.classBuilder(proxyClassName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(parameterizedTypeName) //实现接口
// .addTypeVariable(tTypeVariable)
// .addMethod(constructorMethodSpec)
.addMethod(bindMethodSpec) //添加类中的方法
// .addField(className, "target", Modifier.PRIVATE)
.build();
return typeSpec;
}
本质就是运行时调用编译时生成的文件,而编译生成的文件是通过
@AutoService(Processor.class)
的技术来实现的。
完毕