首先说下butterknife原理
编译时扫描注解并通过javapoet库生成Java代码,调用ButterKnife.bind()方法将ID与对应的上下文绑定在一起(直接就说结论后面还会有人看吗少侠留步,别那么浮躁静下心来慢慢看)
butterknife是一个注解库也就是通过注解来实现View注入的,同为注解库为啥butterknife就这多Star呢,早期的注解库的生命周期是RUNTIME,在运行时通过反射来完成注入,Activity运行时大量使用反射会影响App性能同时产生很多临时对象造成gc,导致界面卡顿。而butterknife采用的是编译时解析技术,在编译时生成代码,所以对运行时没有任何影响。
在分析源码前先简单补习两点知识
元注解
元注解就是注解注解的注解,嗯!
主要有几种其中我们本次讲解用到的有@Target、@Retention。
@Target:标明了注解的使用范围,常用的有
@Retention:用来描述注解的生命周期,有三种
1)SOURCE
被编译器忽略
2)CLASS
注解将会被保留在Class文件中,但在运行时并不会被JVM保留。这是默认行为,所有没有用Retention注解的注解,都会采用这种策略。
3)RUNTIME
保留至运行时。所以我们可以通过反射去获取注解信息。
看下butterknife源码
注解处理器
注解处理器(AbstractProcessor)是javac的一个工具,利用这个工具可以在编译时扫描和处理注解,包括自定义注解,只要定义相应的注解处理器,每个注解处理器都继承AbstractProcessor类,AbstractProcessor类中有几个重要的方法
1)init(ProcessingEnvironment var1)
ProcessingEnvironment是一个接口,里面有三个重要的方法,如图
getElementUtils()方法返回一个Elements,Elements是用于处理Element的工具类,而Element由是什么呢?在注解处理过程中,扫描了所有的java源文件,我的理解是源文件抽象成类就是Element(瑟瑟发抖...不对的话请大佬指出)
Types是用于处理TypeElement的工具类,TypeElement代表源代码中的类型
Filer从文字就可以看出用于创建文件的
2)public abstract boolean process(Set var1, RoundEnvironment var2)
这个方法用于扫描和处理注解,最后会生成我们所需要的java代码,通过RoundEnvironment参数可以查到包含特定注解的原属,需要自定义处理器实现。
3)public Set getSupportedAnnotationTypes()
返回注解所支持的类型?no no no 返回所支持的注解的类型,注意这是不一样的。
4)public SourceVersion getSupportedSourceVersion()
指定所使用的java版本
需要注意的是注解处理器是运行在自己的java虚拟机中的
下面正式开始分析butterknife原理
可以看见这就是butterknife支持的注解
在process方法中会拿到所有的注解信息放到一个Map中,然后遍历Map做相应处理然后生成Java代码
findAndParseTargets(RoundEnvironment env)方法就是用于处理每一个注解的,方法很长,我们只看BindView就可以,其他逻辑一样的
看到主要逻辑都在parseBindView(Elementelement,MapbuilderMap,
SeterasedTargetNames)方法
这个方法会判断被注解的属性是不是privete或static的,如果是就会出错、包名是不是以android或java开头的,是就出错;判断注解的属性是不是一个View,不是则出错
然后int id = element.getAnnotation(BindView.class).value()获取到了要绑定的View的id,根据所在元素查找Builder,对Builder进行判断,如果已经存在了则返回else通过getOrCreateBindingBuilder(builderMap, enclosingElement)方法创建Builder,最后放到集合中
至此已经处理好注解了,下面看下如何生成java代码
在process方法中调用了brewJava方法
可以看到内部调用了createType(int sdk, boolean debuggable)来返回我们需要的类型
里面这个TypeSpec.Builder就是javapoet库构造类的属性用的类,然后就是根据各种判断来创建这个类。119行调用了createBindingConstructor(sdk, debuggable)方法,这个方法就是用于把注解转换成具体代码的,例如把@BindView换成findViewById(),有点长就不贴代码了,方法里面主要做了两件事:判断View是否有监听,如果有就声明称final的;遍历绑定的View,调用addViewBinding生成对应的findViewById方法
洋洋洒洒的把butterknife原理说完了,表达能力有限,反正也没人看就写到这吧