使用Android APT(Annotation Processing Tool)让代码学会自己写代码

来自阿钟的投稿,阅读大约15分钟

APT即注解处理器(Annotation Processing Tool)的简称

简单来说就是个javac的一个工具,可以在代码编译的阶段扫描注解,然后做你想干的事情 比生成代码文件、实现一些功能等等….很多开源框架都应用了这一技术如:Butter Knife、Dagger等等…

一.这篇文章通过实现和Butter Knife一样的自动findViewByid()功能,来了解整个APT的过程

1.先来看下整个项目的模块和依赖关系

图片
  • findview是个Android的Library:处理找控件的具体操作

  • findview-annotation是一个Java的Library:用来存放注解类

  • findview-compiler是一个Java的Library:用来存放注解处理器的

  • sample是一个Android项目:这里就是用来写示例代码的了

2.模块之间的依赖关系如下

  • findview不依赖其他Module

  • findview-annotation不依赖其他Module

  • findview-compiler依赖findview-annotation

  • sample依赖findviewfindview-annotationfindview-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文件中

  1. RetentionPolicy.SOURCE:保留在源文件,当Java文件编译成class文件的时候,注解被遗弃

  2. RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃

  3. 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就可以在看到打印的日志了,如下:

图片

到这里说明注解处理器已经正常工作了,接下来就只要去扫描我们自定义的注解然后做自己想干的事情即可

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的值)

图片

注解所在的包名、类名、控件名称都有了就可以开始生成找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之后就可以看到生成的文件了,在samplebuild\generated\ap_generated_sources\debug\out\com\azhon\sample

图片

四、注解和自动生成找ID的代码都已经生成好了,所以最后一步就是去使用了;那么现在就来看看要怎么使用

  • 在把findview模块创建出来,在这里实现调用逻辑;并创建一个FindView类

  • 通过菜单File —&gt; New Module —&gt; 选择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}

效果图

图片

Demo下载:

https://download.csdn.net/download/a_zhon/12010300

推荐我的慕课网Android实战课程,助你暴力提升Android技术。

https://coding.imooc.com/class/390.html

 

我创建了一个关于Android的交流群,有兴趣可以加我微信我拉你

 

             

 

如果感觉现在的网络技术文章质量不高,苦于自己的Android技术无法得到明显的提升,感叹没有一帮好的学习伙伴及道友,那么我的知识星球可能就是一片净土,好的学习气氛,更好的技术资源与文章,自由且高效率,快来吧。

 

       

阅读原文有惊喜

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值