简介
APT(Annotation Processing Tool)即注解处理器,是一种用来处理注解的工具。JVM会在编译期就运行APT去扫描处理代码中的注解然后输出java文件。
简单来说~~就是你只需要添加注解,APT就可以帮你生成需要的代码
许多的Android开源库都使用了APT技术,如ButterKnife、ARouter、EventBus等
动手实现一个简单的APT
小目标
在使用Java开发Android时,页面初始化的时候我们通常要写大量的view = findViewById(R.id.xx)代码
作为一个优(lan)秀(duo)的程序员,我们现在就要实现一个APT来完成这个繁琐的工作,通过一个注解就可以自动给View获得实例
第零步 创建一个项目
创建一个项目,名称叫 apt_demo
创建一个Activity,名称叫 MainActivity
在布局中添加一个TextView, id为test_textview
第一步 自定义注解
创建一个Java Library Module名称叫 apt-annotation
在这个module中创建自定义注解 @BindView
1 | @Retention(RetentionPolicy.CLASS) |
@Retention(RetentionPolicy.CLASS)
:表示这个注解保留到编译期@Target(ElementType.FIELD)
:表示注解范围为类成员(构造方法、方法、成员变量)
第二步 实现APT Compiler
创建一个Java Library Module名称叫 apt-compiler
在这个Module中添加依赖
1 | dependencies { |
在这个Module中创建BindViewProcessor
类
直接给出代码~~
1 | public class BindViewProcessor extends AbstractProcessor { |
2-1 继承AbstractProcessor抽象类
我们自己实现的APT类需要继承AbstractProcessor
这个类,其中需要重写以下方法:
init(ProcessingEnvironment processingEnv)
getSupportedAnnotationTypes()
getSupportedSourceVersion()
process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)
2-2 init(ProcessingEnvironment processingEnv)方法
这不是一个抽象方法,但progressingEnv参数给我们提供了许多有用的工具,所以我们需要重写它
1 | @Override |
mFilterUtils
文件管理工具类,在后面生成java文件时会用到mTypesUtils
类型处理工具类,本例不会用到mElementsUtils
Element处理工具类,后面获取包名时会用到
限于篇幅就不展开对这几个工具的解析,读者可以自行查看文档~
2-3 getSupportedAnnotationTypes()
由方法名我们就可以看出这个方法是提供我们这个APT能够处理的注解
这也不是一个抽象方法,但仍需要重写它,否则会抛出异常(滑稽),至于为什么有兴趣可以自行查看源码~
这个方法有固定写法~
1 | @Override |
2-4 getSupportedSourceVersion()
顾名思义,就是提供我们这个APT支持的版本号
这个方法和上面的getSupportedAnnotationTypes()类似,也不是一个抽象方法,但也需要重写,也有固定的写法
1 | @Override |
2-5 process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)
最主要的方法,用来处理注解,这也是唯一的抽象方法,有两个参数
set
参数是要处理的注解的类型集合roundEnv
表示运行环境,可以通过这个参数获得被注解标注的代码块
1 | @Override |
process方法的大概流程是:
- 扫描所有被
@BindView
标记的Element - 遍历Element,调用
categories
方法,把所有需要绑定的View变量按所在的Activity进行分类,把对应关系存在mToBindMap中 - 遍历所有Activity的TypeElment,调用
generateCode
方法获得要生成的代码,每个Activity生成一个帮助类
2-6 categories方法
把所有需要绑定的View变量按所在的Activity进行分类,把对应关系存在mToBindMap中
1 | private void categories(Set<? extends Element> elements) { |
注解应该已经很详细了~
实现方式仅供参考,读者可以有自己的实现
2-7 generateCode方法
按照不同的TypeElement生成不同的帮助类(注:参数中的TypeElement对应一个Activity)
1 | private String generateCode(TypeElement typeElement) { |
大家可以对比生成的代码
1 | package com.example.apt_demo; |
有没有觉得字符串拼接很麻烦呢,不仅麻烦还容易出错,那么有没有更好的办法呢(留个坑)
同样的~ 这个方法的设计仅供参考啦啦啦~
第三步 注册你的APT
这应该是最简单的一步
这应该是最麻烦的一步
客官别急,往下看就知道了~
注册一个APT需要以下步骤:
- 需要在 processors 库的 main 目录下新建 resources 资源文件夹;
- 在 resources文件夹下建立 META-INF/services 目录文件夹;
- 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
- 在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;
正如我前面所说的~
简单是因为都是一些固定的步骤
麻烦也是因为都是一些固定的步骤
最后一步 对外提供API
4.1 创建一个Android Library Module,名称叫apt-api
,并添加依赖
1 | dependencies { |
4.2 分别创建launcher、template文件夹
4.3 在template文件夹中创建IBindHelper
接口
1 | public interface IBindHelper { |
这个接口主要供APT生成的帮助类实现
4.4在launcher文件夹中创建AutoBind
类
1 | public class AutoBind { |
这个类是我们的API的入口类,使用了单例模式
inject方法是最主要的方法,但实现很简单,就是通过反射去调用APT生成的帮助类的方法去实现View的自动绑定
完成!拉出来遛遛
在app module里添加依赖
1 | dependencies { |
我们来修改MainActivity中的代码
1 | public class MainActivity extends AppCompatActivity { |
大功告成!我们来运行项目试试看
TextView已经正确显示了文字,我们的小demo到这里就完成啦~
还可以更好
我们的APT还可以变得更简单!
痛点
- 生成代码时字符串拼接麻烦且容易出错
- 继承AbstrctProcessor时要重写多个方法
- 注册APT的步骤繁琐
下面我们来逐个击破~
使用JavaPoet来替代拼接字符串
JavaPoet是一个用来生成Java代码的框架,对JavaPoet不了解的请移步官方文档
JavaPoet生成代码的步骤大概是这样(摘自官方文档):
1 | MethodSpec main = MethodSpec.methodBuilder("main") |
使用JavaPoet来生成代码有很多的优点,不容易出错,可以自动import等等,这里不过多介绍,有兴趣的同学可以自行了解
使用注解来代替getSupportedAnnotationTypes()和getSupportedSourceVersion()
1 | @SupportedSourceVersion(SourceVersion.RELEASE_7) |
这是javax.annotation.processing中提供的注解,直接使用即可
使用Auto-Service来自动注册APT
这是谷歌官方出品的一个开源库,可以省去注册APT的步骤,只需要一行注释
先在apt-compiler模块中添加依赖
1 | dependencies { |
然后添加注释即可
1 | @AutoService(Processor.class) |
总结
APT是一个非常强大而且频繁出现在各种开源库的工具,学习APT不仅可以让我们在阅读开源库源码时游刃有余也可以自己开发注解框架来帮自己写代码~
本文转载自:https://juejin.im/post/5bcdb901f265da0ac8496fed
乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站