ButterKnife源码简析
Github: butterknife 分析版本:fc10f16
ButterKnife 是一个 Android 视图快速注入库,它通过给 View 字段添加注解,可以让我们丢掉 findViewById() 来获取 View 的方法,从而简化了代码。
编译时注解
概述
编译时注解的核心依赖 APT ( Annotation Processing Tools ) 实现,原理是在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查 AbstractProcessor 的子类,并且调用该类型的 process 函数,然后将添加了注解的所有元素都传递到 process 函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是EventBus,Retrofit,Dragger等开源库的基本原理。
创建
创建一个 Java Library
创建一个 annotationcompiler 的 java module
配置 gradle
1 | sourceCompatibility = "1.7" |
同时需要在 app 的 module 中配置一下 Java7
1 | compileOptions { |
创建 Annotation
1 | package com.yydcdut.annotation; |
创建 AbstractProcessor
1 | package com.yydcdut.process; |
注册处理器
在 java module 里的 main 目录中创建一个 resources
文件夹,然后下边在创建 META-INF/services
,创建一个 javax.annotation.processing.Processor
文件,在此文件中写入注解处理器的类全称
1 | com.yydcdut.process.ViewInjectProcessor |
添加 android-apt
在 project 下的 build.gradle 中添加 apt 插件
1 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' |
然后在 app 中的 build.gradle 添加 apt 插件
1 | apply plugin: 'com.android.application' |
添加依赖
在 app 中的 build.gradle 添加依赖
1 | dependencies { |
Code
1 | @SupportedAnnotationTypes("com.yydcdut.annotation.InjectView") |
被注解的部分代码:
1 | package com.yydcdut.textdemo; |
编译之后查看 app/build/generated/source/apt/debug/com/yydcdut/test/generated/GeneratedClass.java
源码解析
ButterKnifeProcessor
这里只分析 BindView
的过程
ButterKnifeProcessor
根据上述创建注解器的流程,那么我们分析源码也有了一个流程,就直接看 ButterKnife 的 AbstractProcessor :
1 | @AutoService(Processor.class)//自动注册处理器 |
在 process
中主要做了两件事,第一是找出被注解的元素,封装成 Map<TypeElement, BindingSet>
,第一个参数可以想成类,第二个参数可以想成所有有关信息的封装;第二就是遍历这个 map ,然后针对每个类生成对应的 ViewBinder 类。
ButterKnifeProcessor#findAndParseTargets
那么主要的查找工作在 findAndParseTargets()
中进行:
1 | public final class ButterKnifeProcessor extends AbstractProcessor { |
RoundEnvironment
可以理解成查询注解信息的类,而 Element
可以理解成程序中的元素,比如包、类、方法等等。
1 | public class ClassA { // TypeElement |
TypeElement 代表源代码中元素类型,但是 TypeElement 并不包含类的相关信息,可以从 TypeElement 获取类的名称,但不能获取类的信息,比如说父类。这些信息可以通过 TypeMirror 获取。你可以通过调用 element.asType()
来获取一个 Element 的 TypeMirror 。
ButterKnifeProcessor#parseBindView
1 | public final class ButterKnifeProcessor extends AbstractProcessor { |
这里是解析被 BindView
修饰的元素,将信息都封装成 BindingSet.Builder
。
BindingSet#newBuilder
1 | final class BindingSet { |
ButterKnifeProcessor#findAndParseTargets
1 | public final class ButterKnifeProcessor extends AbstractProcessor { |
ButterKnifeProcessor#process
findAndParseTargets 算是解析完了,再回过头来看第二步,生成文件的步骤
1 | public final class ButterKnifeProcessor extends AbstractProcessor { |
将信息转变成 JavaFile
,通过 filer 完成。
write过程
1 | final class BindingSet { |
最后文件的生成是通过 com.squareup.javapoet.JavaFile
生成的。
官方 demo 的栗子
1 | package com.example.butterknife.library; |
官方 demo 的 apt 生成类的栗子
1 | // Generated code from Butter Knife. Do not modify! |
ButterKnife
当要进行 bind 的时候,都需要在 Activity 或 View 等的初始化函数中进行绑定,这里就拿 Activity 为栗子进行分析:
1 | public final class ButterKnife { |
最终通过 ClassLoader 的方式将类加载出来,最后 constructor.newInstance 方法来调用该类的构造函数。
总结
平时了解到更多的是运行时注解,即声明注解的生命周期为 RUNTIME ,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是设计到比较多的反射,必然多多少少会有性能的损耗。而 ButterKnife 用的 APT 编译时解析技术,比较好的解决了反射这些问题。
APT 大概就是声明的注解的生命周期为 CLASS ,然后继承 AbstractProcessor 类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用 AbstractProcessor 的 process 方法,对注解进行处理,那么就可以在处理的时候,动态生成绑定事件或者控件的 java 代码,然后在运行的时候,直接调用 bind 方法完成绑定。
其实这种方式的好处是我们不用再一遍一遍地写 findViewById 和 onClick 等代码了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。