概要
本篇博客主要包括以下三个部分
- 日常使用 ButterKnife 的方式以及 ButterKnife 的 bind 方法
- Xxx_ViewBinding 类是如何生成的?
- 注解处理器是怎么注册的?
其中的代码基于 ButterKnife 8.6.0
本文假设你已经对注解有所了解。不了解注解的同学可以先看看这篇文章 ,我们主要关注编译期注解。
运行期 ButterKnife#bind
我们通常都是通过以下方式使用 ButterKnife 的。将要绑定的控件加上 @BindView 注解,在 onCreate 方法 完成 setContentView(R.layout.activity_main); 之后。调用 ButterKnife.bind(this); 对相应的 View 进行初始化。
|
|
public static Unbinder bind(@NonNull Activity target) ,在 Activity 中以该方法作为入口,首先获取 与 Activity 相关联的 window,然后再通过 window 获取 DecorView。最后通过 createBinding 方法创建相关绑定。
|
|
|
|
|
|
以下为编译器生成的 MainActivity_ViewBinding 类,该类在构造方法中对那些在 MainActivity 中打了注解(例如:@BindView)的 View 进行了初始化。
|
|
这里插一个小话题:通常我们都会把不需要被外部使用到的成员变量声明为 private 的,但是回头看看我们使用 @BindView的 View 访问权限均为 defalut 。为什么呢?从 MainActivity_ViewBinding 的构造方法中可以看到对 View 进行赋值时直接使用了 target.mBtn、 target.mRadarView,如果声明为 private ,那就需要通过反射才能访问到对应的 View,这样会有性能上的损失。
小结
在运行时以 ButterKnife#bind 方法作为入口,首先以目标 class 为 key 到缓存(一个 LinkedHashMap)中查找相应的 binding class,如果命中直接返回。如果没有缓存,则通过类加载器加载对应的 Xxx_ViewBinding 类,并将其加入到缓存中,然后返回。最后调用 Xxx_ViewBinding 类的构造器(通过反射)创建一个实例。Xxx_ViewBinding 类的构造器中完成了对 View 的绑定。比如 findViewById,setOnClickListener
编译期生成代码
Q:前面提到的 MainActivity_ViewBinding 类是如何生成的呢?
Answer: 在编译时对类中的 Annotation 进行解析,通过 JavaPoet 生成相应的代码。
编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,由编译器自动解析。开发人员需要做的
a. 自定义类继承自 AbstractProcessor
b. 重写其中的 process 函数
c. 注册注解处理器。注册完成后,编译器在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理
自定义 Annotation 用于存储元数据
以 BindView 注解为例
|
|
@Retention(CLASS):表示保留到编译期@Target(FIELD):表示作用域为成员变量
注解处理器 ButterKnifeProcessor
ButterKnifeProcessor 继承自 AbstractProcessor ,AbstractProcessor 的主要源码如下,我们主要关注四个方法
|
|
init(ProcessingEnvironment env):每个注解处理器都必须有个空的构造方法。不过,有一个特殊的 init 方法,它会被注解处理器工具传入一个 ProcessingEnvironment 作为参数来调用。ProcessingEnvironment 提供了一些有用的工具类,如 Elements,Types 和 Filter。我们后面会用到它们。process(Set<? extends TypeElement> annotations, RoundEnvironment env):这个方法可以看做每个处理器的 main 方法。你要在这里写下你的扫描,判断和处理注解的代码,并生成 java 文件。通过传入的 RoundEnvironment 参数,你可以查询使用了某个特定注解的所有元素,我们稍后会看到。getSupportedAnnotationTypes( ):这里你需要说明这个处理器需要针对哪些注解来注册。注意返回类型是一个字符串的 Set,包含了你要用这个处理器处理的注解类型的全名getSupportedSourceVersion( ):用于指定你使用的 java 版本。通常会选择返回SourceVersion.latestSupported( )。当然,你也可以指定具体 java 版本:比如return SourceVersion.RELEASE_7;
ButterKnife 的解析工作就是通过 ButterKnife#process 方法实现的。
|
|
process 方法首先调用 findAndParseTargets 方法获取解析的目标类
|
|
上面的方法是以注解为单位,处理各个使用了注解的属性,然后将它们存放到一个 Map 中。(该 Map 以属性所在类为 key,以属性集 BindingSet.Builder 为 value)。那么具体是如何解析的呢?
对于 ButterKnife 我们最常用的是 @BindView,针对 BindView 我们主要看 parseBindView 方法,该方法将使用了@BindView 的属性的绑定信息,存放到以该属性所在类为 key ,值为 BindingSet.Builder 的Map 中,供后续 javaPoet 生成代码使用。
|
|
假设我们在某一个类中重复绑定了相同的 id,比如下面这个例子对 R.id.app_bar_layout 进行了两次绑定:
|
|
在编译期会报如下错误:
Error:(36, 18) 错误: Attempt to use @BindView for an already bound ID 2131230757 on 'mAppBarLayout'. (com.android.rdc.librarysystem.MainActivity.mAppBarLayout2)
小结
ButterKnife 是以这样的方式来处理注解的:ButterKnife 支持多种注解(比如 @BindView 、@BindColor),这些注解被按照一定的顺序处理。获取使用了某个注解的所有 Element,对该 Element 的进行处理,生成相应的 FieldXxxBinding 然后将它添加到该 Element 所在类的 BindingSet.Builder 中。
也就是说,ButterKnife 是以注解为单位进行解析,处理完了,就把它放到相应属性所在类对应的 Set 里面(而不是一次扫描一个类,然后对该类中属性使用到的注解进行处理,一次性生成某个类的所对应的 BindingSet.Builder )。
通过 JavaPoet 生成 java 文件
正如其名,poet 指诗人,也就是作诗的人。java poet 指的是能够自动写 java 源代码的库。
javapoet 里面常用的几个类:
TypeNameJava 系统的所有类型,加上 void 类型MethodSpec代表一个构造函数或方法声明。TypeSpec代表一个类,接口,或者枚举声明。FieldSpec代表一个成员变量,一个字段声明。JavaFile包含一个顶级类的 Java 文件。
回到前面的 process 方法,可以看到 JavaFile javaFile = binding.brewJava(sdk); javaFile.writeTo(filer);
BindingSet#brewJava 。该方法通过 Javapoet 生成一个 java 文件
|
|
关于 javapoet 的介绍详见这篇文章
注解处理器的注册
方法 1:使用 google 提供的注册处理器库
最简单的方式是使用 google 提供的一个注册处理器的库。 compile 'com.google.auto.service:auto-service:1.0-rc2'
然后@AutoService(Processor.class):向 javac 注册我们这个自定义的注解处理器,这样,在 javac 编译时,才会调用到我们这个自定义的注解处理器方法。
AutoService 这里主要是用来生成META-INF/services/javax.annotation.processing.Processor文件的。
方法 2:手动注册
如果不使用上述处理库,那么,你需要自己进行手动配置进行注册,具体手动注册方法如下:
1.创建一个META-INF/services/javax.annotation.processing.Processor 文件,
其内容是一系列的自定义注解处理器完整有效类名集合,以换行切割:
|
|
2.将自定义注解处理器和
META-INF/services/javax.annotation.processing.Processor 打包成一个.jar 文件。所以其目录结构大概如下所示:
|
|
ButterKnife 使用的是第一种方式。
|
|
总结
ButterKnife 在编译的时候帮我们自动生成了绑定的代码,然后在运行的时候调用就行了。
- 首先我们在需要绑定 View 的 地方使用 @BindView 或者 ButterKnife 提供的其他注解,比如 @BindColor
- 在编译期 ButterKnifeProcessor 的 process 函数会被调用,在其中获取使用了相应注解的相关方法/成员变量信息,通过 javapoet 生成 Xx_ViewBinder 和 Xx_ViewBinding 类。
- 运行时,onCreate 方法中通常需要
ButterKnife.Bind()方法,从此处进入,通过 反射调用 Xx_ViewBinding 的构造方法对 View 进行初始化。
参考资料与学习资源推荐
由于本人水平有限,可能出于误解或者笔误难免出错,如果发现有问题或者对文中内容存在疑问欢迎在下面评论区告诉我,请对问题描述尽量详细,以帮助我可以快速找到问题根源。谢谢!