本文主要介绍APT技术与ButterKnife的源码简析。
注解处理器(Annotation Processor)
注解
关于注解这里不做介绍,参考这篇文章:简单介绍 Java 中的注解 (Annotation)
注解处理器
如果你对注解有一定了解的话,应该知道我们在运行时(Runtime)获取注解信息的方式是反射。如果我们想要在编译期(Compile time)就获取到注解的信息,那么我们可以使用注解处理器来做这样的工作。注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以对自定义注解,并注册相应的注解处理器。
一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是
.java文件)作为输出。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。
注解处理器工作流
Java代码编译的过程主要包括三步,解析、注解处理、字节码生成,具体的流程见下图。

这里需要循环的处理注解,原因是注解处理器可能会生成新的Java文件,这些新的Java文件也要进行一遍语法分析,并且有可能这些新的Java文件也包含注解,还要继续被注解处理器分析处理。
JavaPoet
前面提到注解处理这一步可以生成Java文件,但是实际上注解处理器只提供了注解以及与其相关的Token信息,还有一个Java文件的输出流。你可以直接通过这些必要的信息,将代码以字符流的形式写入Java文件,但是这样的过程无疑是吃力不讨好的(代码编写烦琐且难以维护)。
JavaPoet是square开源的一个库,作用就如其名,让你在这个过程,像写诗一样,优雅的将Java代码字符流写入Java文件。
使用的细节不再赘述,可以直接参考官方文档:square/javapoet/README.md
实现一个简单的ViewBinder
上一节是从概念上介绍了一下注解处理器,这一节主要是介绍通过注解处理器实现一个类似于ButterKnife的 @BindView功能的注解。具体的实现参考:xybean/MiniViewBinder
自定义注解处理器
1、module设计
项目的module最好分为三个:
- 一个纯Java模块,命名为viewbinder-annotations,用于放置需要开放给外部的Annotation。
- 一个纯Java模块,命名为viewbinder-compiler,用于放置注解处理器的实现。
- 一个Android模块,用于使用生成的类,并开放接口给外部使用(外部不直接使用生成类)。
2、添加依赖
viewbinder-compiler模块需要添加依赖
1 | compile 'com.squareup:javapoet:1.10.0' |
3、实现注解
在viewbinder-annotations添加注解实现,注意元注解的值。
1 | (RetentionPolicy.SOURCE) |
4、实现注解处理器
实现注解处理器只要继承AbstractProcessor并将其注册到编译器即可。
1 | // 将当前注解处理器注册到编译器中 |
5、注解处理器的调试
参考文章: Android studio 下调试注解处理器
注意在调试前先启动自己设置的远程调试器,然后再build项目。
生成类
生成类的思路是,在ViewBinderProcessor.process()中去的注解字段以及其所在类的信息,然后根据这些信息生成XXX__ViewBinder.java。这里的生成类的名字是我们自定义的,需要满足一定的生成规则,这样才能在viewbinder模块中被正确调用。
在处理过程中,会缓存注解相关信息,并且处理器会被多次调用,使用的是同一个处理器实例,因此需要在每次处理完后清空这些缓存的数据,以免重复生成文件导致错误。
使用生成类
在实现了注解处理器,并将注解使用在主项目中后,点击build按钮,就会在主项目的build/generated/res/source/apt目录下生成我们的生成类。
如果要使用生成类,需要在viewbinder模块中使用反射调用,可见生成类的命名规范性是非常重要的。
ButterKnife源码简析
这一节的源码分析主要是分析注解处理的逻辑与View绑定的流程。
总体流程图
解析注解
1 | // 以ButterKnifeProcessor.process()为入口 |
关键的解析过程实现在findAndParseTargets()中,它对不同类型的注解分别作了处理,然后汇总所有的解析结果到Map<TypeElement, BindingSet>中。这里我们只关注BindView相关的实现,也就是方法parseBindView
的实现。
这个方法的大致可以概括为:

1 | // 做过简化处理的源代码,实际实现处理细节会更多 |
生成类
生成类的主要逻辑在BindingSet.createType()中:
1 | private TypeSpec createType(int sdk, boolean debuggable, boolean useAndroidX) { |
生成类的关键在于针对注解所在类的不同做不同的处理,具体的看生成类的样式就知道了:
1 | // 如果是Activity |
1 | // 如果是View |
1 | // 如果是Dialog |
使用生成类
使用生成类的方法在ButterKnife.createBinding()中,具体的逻辑就是查找到的输入对象target对应的生成类,然后调用生成类的构造方法实现注入。
1 | private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { |
这里的关键实现在于如何找到对应的生成类,查看方法findBindingConstructorForClass
1 | private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { |