2.2 编译时注解
运行时注解**RUNTIME如上2.1所示,大多数时候实在运行时使用反射来实现所需效果,这很大程度上影响效率,如果BufferKnife的每个View注入不可能如何实现。实际上,ButterKnife使用的是编译时注解***CLASS***,如下***图X2.2***,是ButterKnife的@BindView
注解,它是一个编译时注解,在编译时生成对应java代码,实现注入。
说到编译时注解,就不得不说注解处理器*** AbstractProcessor***,如果你有注意,一般第三方注解相关的类库,如bufferKnike、ARouter,都有一个Compiler命名的Module,如下图X2.3,这里面一般都是注解处理器,用于编译时处理对应的注解。
注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以对自定义注解,并注册相应的注解处理器,用于处理你的注解逻辑。
如下所示,实现一个自定义注解处理器,至少重写四个方法,并且注册你的自定义Processor,详细可参考下方代码CustomProcessor
。
-
@AutoService(Processor.class),谷歌提供的自动注册注解,为你生成注册Processor所需要的格式文件(
com.google.auto
相关包)。 -
init(ProcessingEnvironment env),初始化处理器,一般在这里获取我们需要的工具类。
-
getSupportedAnnotationTypes(),指定注解处理器是注册给哪个注解的,返回指定支持的注解类集合。
-
getSupportedSourceVersion() ,指定java版本。
-
process(),处理器实际处理逻辑入口。
@AutoService(Processor.class)
public class CustomProcessor extends AbstractProcessor {
/**
* 注解处理器的初始化
* 一般在这里获取我们需要的工具类
* @param processingEnvironment 提供工具类Elements, Types和Filer
*/
@Override
public synchronized void init(ProcessingEnvironment env){
super.init(env);
//Element代表程序的元素,例如包、类、方法。
mElementUtils = env.getElementUtils();
//处理TypeMirror的工具类,用于取类信息
mTypeUtils = env.getTypeUtils();
//Filer可以创建文件
mFiler = env.getFiler();
//错误处理工具
mMessages = env.getMessager();
}
/**
* 处理器实际处理逻辑入口
* @param set
* @param roundEnvironment 所有注解的集合
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annoations,
RoundEnvironment env) {
//do someThing
}
//指定注解处理器是注册给哪个注解的,返回指定支持的注解类集合。
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> sets = new LinkedHashSet<String>();
//大部分class而已getName、getCanonicalNam这两个方法没有什么不同的。
//但是对于array或内部类等就不一样了。
//getName返回的是[[Ljava.lang.String之类的表现形式,
//getCanonicalName返回的就是跟我们声明类似的形式。
sets(BindView.class.getCanonicalName());
return sets;
}
//指定Java版本,一般返回最新版本即可
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
首先,我们梳理下一般处理器处理逻辑:
- 1、遍历得到源码中,需要解析的元素列表。
- 2、判断元素是否可见和符合要求。
- 3、组织数据结构得到输出类参数。
- 4、输入生成java文件。
- 5、错误处理。
然后,让我们理解一个概念:Element
,因为它是我们获取注解的基础。
Processor处理过程中,会扫描全部Java源码,代码的每一个部分都是一个特定类型的Element,它们像是XML一层的层级机构,比如类、变量、方法等,每个Element代表一个静态的、语言级别的构件,如下方代码所示。
package android.demo; // PackageElement
// TypeElement
public class DemoClass {
// VariableElement
private boolean mVariableType;
// VariableElement
private VariableClassE m VariableClassE;
// ExecuteableElement
public DemoClass () {
}
// ExecuteableElement
public void resolveData (Demo data //TypeElement ) {
}
}
其中,Element
代表的是源代码,而TypeElement
代表的是源代码中的类型元素,例如类。然而,TypeElement
并不包含类本身的信息。你可以从TypeElement
中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过TypeMirror
获取。你可以通过调用elements.asType()
获取元素的TypeMirror
。
1、知道了Element
,我们就可以通过process 中的RoundEnvironment
去获取,扫描到的所有元素,如下图X2.4,通过env.getElementsAnnotatedWith
,我们可以获取被@BindView注解的元素的列表,其中validateElement
校验元素是否可用。
2、因为env.getElementsAnnotatedWith
返回的,是所有被注解了@ BindView的元素的列表。所以有时候我们还需要走一些额外的判断,比如,检查这些Element是否是一个类:
@Override
public boolean process(Set<? extends TypeElement> an, RoundEnvironment env) {
for (Element e : env.getElementsAnnotatedWith(BindView.class)) {
// 检查元素是否是一个类
if (ae.getKind() != ElementKind.CLASS) {
...
}
}
...
}
3、javapoet (com.squareup:javapoet
)是一个根据指定参数,生成java文件的开源库,有兴趣了解javapoet的可以看下javapoet——让你从重复无聊的代码中解放出来,在处理器中,按照参数创建出 JavaFile
之后,通Filer
利用javaFile.writeTo(filer);
就可以生成你需要的java文件。
4、错误处理,在处理器中,我们不能直接抛出一个异常,因为在process()中抛出一个异常,会导致运行注解处理器的JVM崩溃,导致跟踪栈信息十分混乱。因此,注解处理器就有一个Messager类,一般通过messager.printMessage( Diagnostic.Kind.ERROR, StringMessage, element)
即可正常输出错误信息。
至此,你的注解处理器完成了所有的逻辑。可以看出,编译时注解实在编译时生成java文件,然后将生产的java文件注入到源码中,在运行时并不会像运行时注解一样,影响效率和资源。
总结
我们就利用ButterKnife的流程,简单举例做个总结吧。
- 1、
@BindView
在编译时,根据Acitvity生产了XXXActivity$$ViewBinder.java。 - 2、Activity中调用的
ButterKnife.bind(this);
,通过this的类名字,加$$ViewBinder,反射得到了ViewBinder
,和编译处理器生产的java文件关联起来了,并将其存在map中缓存,然后调用ViewBinder.bind()
。 - 3、在ViewBinder的bind方法中,通过id,利用ButterKnife的
butterknife.internal.Utils
工具类中的封装方法,将findViewById()控件注入到Activity的参数中。
好了,通过上面的流程,是不是把编译时注解的生成和使用连接起来了呢?有问题还请各位留言谈论。
参考资料
————————分割线————————
如果你看到了这里,觉得文章写得不错就给个关注呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能关注下我,以后还会更新技术干货,谢谢您的支持!
转发分享+关注,每天获取更多资料
Android架构师之路很漫长,一起共勉吧!