这次要拆的轮子是android中大名鼎鼎的“Butterknife”。
讲解按照,先结论,后分析原理,然后总结源码中学习到的东西,造一个自己的小轮子。
1.结论:Butterknife属于基于注解的编译框架,相比一些通过反射实现的运行时注解框架,性能高出一大截。
Butterknife是利用注解处理工具(APT)扫描和处理我们自定义的BindView注解,然后更加JavaPoet自动生成目标代码文件。
然后利用Butterknife.bind(Object)中通过反射的方式调用自动生成的文件,达到注解要实现的目的,比如通过@BindView(R.id.tv)来简化findViewById的大量重复繁琐的工作。
整体的流程:
2.既然是基于注解的,首先当然是自定义注解
这里会补充一部分注解的知识:
要想注解能够正常工作,还需要介绍一下一个新的概念那就是元注解
元注解是什么意思呢?元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面.
元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种
@Retention 保留期的意思,当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Target
Target 是目标的意思,@Target 指定了注解运用的地方。
你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
@Target 有下面的取值
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
其他的几个元注解的用途和概念大家自己去查,这里就不在赘述了。
根据上面的注解知识我们可以知道,上面我们自定义的注解保留期是在编译期间,作用在属性上。
发现注解中还有一个属性值。
知识补充:注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
如下:
使用的时候。如下:
赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开
需要注意的是,在注解中定义属性时它的类型必须是 基本数据类型外加 类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:
TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。
它可以这样应用。
@TestAnnotation()
public class Test {
}
因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了,这一步可以省略。
另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用
等效如下图
最后,还需要注意的一种情况是一个注解没有任何属性。比如
那么在应用这个注解的时候,括号都可以省略
注解的属性就介绍到这里。
3.先从生成的文件入手查看
文件目录如下:
打开生成文件:
推测:在使用的时候,应该就是通过ButterKnife.bind(this);来调用这个自动生成文件。
上面这个生成的文件,我们稍后后头再来看
那么我们先来看一下:ButterKnife.bind(this);是如何调用生成的文件的。
跟进源码:
接着查看findBindingConstructorForClass这个函数如下:
可以发现
该类维护了一个缓存
先从缓存中更加cls也就是要绑定的类中获取到继承自Unbinder的Constructor的实例,如何获取的了,直接重用,如果没有,直接通过Class.forName(clsName +"_ViewBinding")拼接新的类名。然后通过bindingClass.getConstructorgou构造新的Constructor,并将其添加到缓存中,重复利用,提供效率。
然后查看createBinding这个函数发现:如下图:
constructor.newInstance(target, source)相当于调用如下代码:
达到了绑定控件的操作
查看findRequiredViewAsType
跟进findRequiredView
到这就看到我们熟悉的那一句findViewById,回到源码05中
return castView(view, id, who, cls);
跟进源码中查看:
再次跟进cls.cast(view);
发现这个就是类型强转
到此,已经分析完了。
我们来过一下流程:
Butterknife.bind(this)------>createBinding(target, source)
->Constructor constructor =findBindingConstructorForClass(targetClass)
-->constructor.newInstance(target, source)
-->生成文件的类的构造函数MainActivity_ViewBinding(MainActivity target, View source)
上面讲的是如果在代码中通过简单的已经Butterknife.bind(this)来调用生成java文件的类的实例来达到绑定控件的。
那么这个java文件是如何生成的呢,那么下一节,我们讲详细介绍如何通过APT扫描注解,收集注解信息,处理注解,并且利用javapoet来生成我们目标文件。