一起拆轮子玩(一)


 这次要拆的轮子是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 这个属性。所以可以这样应用


单属性

等效如下图


value

最后,还需要注意的一种情况是一个注解没有任何属性。比如


无属性值


那么在应用这个注解的时候,括号都可以省略


无属性值的注解

注解的属性就介绍到这里。


3.先从生成的文件入手查看

文件目录如下:


生成文件

打开生成文件:

生成文件1
生成文件2

推测:在使用的时候,应该就是通过ButterKnife.bind(this);来调用这个自动生成文件。

上面这个生成的文件,我们稍后后头再来看

那么我们先来看一下:ButterKnife.bind(this);是如何调用生成的文件的。

跟进源码:



源码01

接着查看findBindingConstructorForClass这个函数如下:


源码02

可以发现

该类维护了一个缓存

源码03

先从缓存中更加cls也就是要绑定的类中获取到继承自Unbinder的Constructor的实例,如何获取的了,直接重用,如果没有,直接通过Class.forName(clsName +"_ViewBinding")拼接新的类名。然后通过bindingClass.getConstructorgou构造新的Constructor,并将其添加到缓存中,重复利用,提供效率。

然后查看createBinding这个函数发现:如下图:


源码04

constructor.newInstance(target, source)相当于调用如下代码:


源码05

达到了绑定控件的操作

查看findRequiredViewAsType


源码06

跟进findRequiredView


源码07

到这就看到我们熟悉的那一句findViewById,回到源码05中

return castView(view, id, who, cls);

跟进源码中查看:


y源码08

再次跟进cls.cast(view);


源码09

发现这个就是类型强转

到此,已经分析完了。

我们来过一下流程:

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来生成我们目标文件。

你可能感兴趣的:(一起拆轮子玩(一))