来自阿钟的投稿,阅读大约15分钟
APT即注解处理器(Annotation Processing Tool)的简称
简单来说就是个javac的一个工具,可以在代码编译的阶段扫描注解,然后做你想干的事情 比生成代码文件、实现一些功能等等….很多开源框架都应用了这一技术如:Butter Knife、Dagger等等…
一.这篇文章通过实现和Butter Knife一样的自动findViewByid()功能,来了解整个APT的过程
1.先来看下整个项目的模块和依赖关系
![图片](https://i-blog.csdnimg.cn/blog_migrate/9669dcc9274e00a3e2b942923713440d.png)
findview
是个Android的Library:处理找控件的具体操作findview-annotation
是一个Java的Library:用来存放注解类findview-compiler
是一个Java的Library:用来存放注解处理器的sample
是一个Android项目:这里就是用来写示例代码的了
2.模块之间的依赖关系如下
findview
不依赖其他Modulefindview-annotation
不依赖其他Modulefindview-compiler
依赖findview-annotation
sample
依赖findview
、findview-annotation
、findview-compiler
二、那就开始来把Module一个个创建了
1.首先创建 findview-annotation
模块并创建一个BindView
注解类
1//作用在属性之上
2@Target(ElementType.FIELD)
3//编译期
4@Retention(RetentionPolicy.CLASS)
5public @interface BindView {
6 int value();
7}
@Target()这个属性表明注解可以作用于类,方法,变量,参数….等等;这里表明作用于变量之上
@Retention()这个属性表明注解需要在什么时期保留,这里表明保留到class文件中
RetentionPolicy.SOURCE:保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃
RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
三、继续创建findview-compiler
模块并创建一个注解处理器
通过菜单
File —> New Module —> 选择Java Library
创建即可还需要依赖google提供的两个注解处理器工具,gradle文件内容如下
同时依赖
findview-annotation
模块
1apply plugin: 'java-library'
2dependencies {
3 implementation fileTree(dir: 'libs', include: ['*.jar'])
4 //google
5 compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
6 annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
7 //依赖注解Module
8 implementation project(path: ':findview-annotation')
9}
10//解决乱码
11tasks.withType(JavaCompile) {
12 options.encoding = "UTF-8"
13}
14sourceCompatibility = "7"
15targetCompatibility = "7"
1.创建一个FindViewProcessor
处理器来处理上面定义的BindView
注解
1@AutoService(Processor.class)
2//需要扫描哪些注解
3@SupportedAnnotationTypes("com.azhon.findview.annotation.BindView")
4//指定jdk的编译版本
5@SupportedSourceVersion(SourceVersion.RELEASE_8)
6public class FindViewProcessor extends AbstractProcessor {
7
8 //操作Element工具
9 private Elements elementUtils;
10 //类信息工具
11 private Types typeUtils;
12 //日志工具
13 private Messager messager;
14 //文件生成工具
15 private Filer filer;
16 @Override
17 public synchronized void init(ProcessingEnvironment processingEnv) {
18 super.init(processingEnv);
19 elementUtils = processingEnv.getElementUtils();
20 typeUtils = processingEnv.getTypeUtils();
21 messager = processingEnv.getMessager();
22 filer = processingEnv.getFiler();
23 //打印日志
24 messager.printMessage(Diagnostic.Kind.NOTE, "FindViewProcessor");
25 }
26 @Override
27 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
28 return false;
29 }
30}
@AutoService(Processor.class) 表示这个是一个注解处理器,这样当编译项目的时候这个地方的代码就会去执行了
@SupportedAnnotationTypes("com.azhon.findview.annotation.BindView")需要扫描的注解类路径
@SupportedSourceVersion(SourceVersion.RELEASE_8)指定jdk的编译版本
2.让sample
示例代码的gradle去依赖注解和注解处理器模块
sample/build.gradle文件
1implementation project(path: ':findview-annotation')
2//依赖注解处理器
3annotationProcessor project(path: ':findview-compiler')
3.点击菜单的Build —> Rebuild Project
就可以在看到打印的日志了,如下:
![图片](https://i-blog.csdnimg.cn/blog_migrate/3936c95c200483296d9f191b98415d61.png)
到这里说明注解处理器已经正常工作了,接下来就只要去扫描我们自定义的注解然后做自己想干的事情即可
4.要想注解处理器扫描得到注解,我们就得先去使用它
在
sample
模块中的build.gradle
文件依赖注解和注解处理器
1//依赖注解
2implementation project(path: ':findview-annotation')
3//依赖注解处理器
4annotationProcessor project(path: ':findview-compiler')
使用就就很简单了,布局就一个
TextView
然后给个id为tv
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 tools:context=".MainActivity">
8 <TextView
9 android:id="@+id/tv"
10 android:layout_width="wrap_content"
11 android:layout_height="wrap_content"
12 android:text="Hello World!"
13 app:layout_constraintBottom_toBottomOf="parent"
14 app:layout_constraintLeft_toLeftOf="parent"
15 app:layout_constraintRight_toRightOf="parent"
16 app:layout_constraintTop_toTopOf="parent" />
17</androidx.constraintlayout.widget.ConstraintLayout>
在MainActivity中使用自定义的注解
1public class MainActivity extends AppCompatActivity {
2
3 @BindView(R.id.tv)
4 TextView textView;
5 @Override
6 protected void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.activity_main);
9 }
10}
5.回到FindViewProcessor
处理器中,通过代码找到的我们上面标记的TextView
1@Override
2public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
3 //获取所有使用到注解的节点
4 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
5 //遍历所有的类节点
6 for (Element element : elements) {
7 //类的的包名
8 String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
9 //类名
10 String clsName = element.getEnclosingElement().getSimpleName().toString();
11 String simpleName = element.getSimpleName().toString();
12 int value = element.getAnnotation(BindView.class).value();
13 messager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
14 messager.printMessage(Diagnostic.Kind.NOTE, "clsName:" + clsName);
15 messager.printMessage(Diagnostic.Kind.NOTE, "simpleName:" + simpleName);
16 messager.printMessage(Diagnostic.Kind.NOTE, "value:" + value);
17 }
18 return false;
19}
Rebuild之后(value就是R.id.tv的值)
![图片](https://i-blog.csdnimg.cn/blog_migrate/97fef37a2a599c8570a72974316dcad1.png)
注解所在的包名、类名、控件名称都有了就可以开始生成找ID的代码了;也就是生成一个Java文件然后实现找ID的代码
6.一个Java类的结构是从上到下一般是由 包名、导包、类名、变量、方法等组成,所以我们生成类时也需要这样来生成
1@Override
2public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
3 //获取所有使用到注解的节点
4 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
5 //遍历所有的类节点
6 for (Element element : elements) {
7 //类的的包名
8 String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
9 //类名
10 String clsName = element.getEnclosingElement().getSimpleName().toString();
11 String simpleName = element.getSimpleName().toString();
12 int value = element.getAnnotation(BindView.class).value();
13 try {
14 createJavaFile(packageName, clsName, simpleName, value);
15 } catch (Exception e) {
16 e.printStackTrace();
17 }
18 }
19 return false;
20}
21/**
22 * 生成java文件
23 *
24 * @param packageName
25 * @param clsName
26 * @throws IOException
27 */
28private void createJavaFile(String packageName, String clsName, String simpleName, int value)
29 throws IOException {
30 String className = clsName + "$$ViewBinding";
31 JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + className);
32 Writer writer = sourceFile.openWriter();
33 writer.write("package " + packageName + ";\n");
34 writer.write("import android.view.View;\n");
35 writer.write("public class " + className + "{\n");
36 writer.write("public " + className + "(" + clsName + " target){\n");
37 writer.write("this(target,target.getWindow().getDecorView());\n}\n");
38 writer.write("public " + className + "(" + clsName + " target, View view){\n");
39 writer.write("target." + simpleName + "=view.findViewById(" + value + ");\n }");
40 writer.write("}");
41 writer.close();
42}
再次Rebuild之后就可以看到生成的文件了,在sample
的build\generated\ap_generated_sources\debug\out\com\azhon\sample
下
![图片](https://i-blog.csdnimg.cn/blog_migrate/08048cc03cb1b17c03d9dfe6c2e06ba1.png)
四、注解和自动生成找ID的代码都已经生成好了,所以最后一步就是去使用了;那么现在就来看看要怎么使用
在把
findview
模块创建出来,在这里实现调用逻辑;并创建一个FindView类
通过菜单
File —> New Module —> 选择Java Library
创建即可
1public class FindView {
2
3 public static void bind(Activity activity) {
4 Constructor constructor = findBindingConstructorForClass(activity.getClass());
5 if (constructor != null) {
6 try {
7 //实例画APT生成的类 即会自动找id
8 constructor.newInstance(activity);
9 } catch (Exception e) {
10 e.printStackTrace();
11 }
12 }
13 }
14 /**
15 * 根据当前类名找到APT生成的类
16 *
17 * @param cls
18 * @return
19 */
20 public static Constructor findBindingConstructorForClass(Class<?> cls) {
21 String clsName = cls.getName();
22 try {
23 Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "$$ViewBinding");
24 return bindingClass.getConstructor(cls);
25 } catch (Exception e) {
26 e.printStackTrace();
27 }
28 return null;
29 }
30}
APT生成的类是我们自定义的规则
Activity类名+$$ViewBinding
,所以可以通过类加载机制加载到每个Activity对应生成的类通过反射实例化类的构造方法传入对应的参数,这样就调用到了找ID的代码
1.sample
模块依赖FindView
模块
1implementation project(path: ':findview')
然后使用即可,与ButterKnife使用是一致的
1public class MainActivity extends AppCompatActivity {
2 @BindView(R.id.tv)
3 TextView textView;
4 @Override
5 protected void onCreate(Bundle savedInstanceState) {
6 super.onCreate(savedInstanceState);
7 setContentView(R.layout.activity_main);
8 FindView.bind(this);
9 textView.setText("成功设置了文本");
10 }
11}
效果图
![图片](https://i-blog.csdnimg.cn/blog_migrate/817283fa1375f6ee2a6269257c346f4f.png)
Demo下载:
https://download.csdn.net/download/a_zhon/12010300
推荐我的慕课网Android实战课程,助你暴力提升Android技术。
https://coding.imooc.com/class/390.html
我创建了一个关于Android的交流群,有兴趣可以加我微信我拉你
如果感觉现在的网络技术文章质量不高,苦于自己的Android技术无法得到明显的提升,感叹没有一帮好的学习伙伴及道友,那么我的知识星球可能就是一片净土,好的学习气氛,更好的技术资源与文章,自由且高效率,快来吧。
阅读原文有惊喜