关于注解使用必须了解的——Annotation、AbstraceProcessor、APT

一.前言:   

     了解一个东西通常是因为它有用,我主要是为了了解现在的一些主流框架(如butterknife)的实现原理才关注Annotation,所以这篇文章是记录我在实现注解内容获取时遇到的问题。因此看这篇文章的朋友首先你需要了解Annotation的基础知识,包括什么是注解,元注解,自定义注解。靠,什么都知道了,还看这篇文章作甚?这篇文章主要是记录什么问题的呢?ok,看一下下面这段代码:   

class ExampleActivity extends Activity {

  @BindView(R.id.user) 

  EditText username;

  @BindView(R.id.pass) 

  EditText password;

...

}

      用过butterknife 的想必都不会陌生,而了解butterknife 的想必也都知道这是注解在框架中的应用。@BindView(R.id.user)这一句帮我们实现了findViewById()的功能,具体怎么实现的不是今天的内容,但是可以肯定的是一定要获取到括号里的内容,这就是今天要说的,即注解的获取。

注解的保留策略有三种:

@Retention(RetentionPolicy.SOURCE)   //注解仅存在于源码中,在class字节码文件中不包含,这个不作讲解

@Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得。可在编译时获取

@Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

       今天主要讲的是RetentionPolicy.CLASS策略,在此之前先看一个RetentionPolicy.RUNTIME策略的注解获取的实现方式,方便了解我们要做的事情,后面对比:

  运行时注解:

  1.定义一个注解

  

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

public @interface BindId {

    int value();

}

   2.定义一个类使用该注解

 public class User {


    @BindId(101)

    private int viewId1;

    @BindId(102)

    private int viewId2;

    @BindId(103)

    private int viewId3;

    @BindId(104)

    private int viewId4;

}

  3.获取注解内容

public class TestA {

    public static void main(String[] s){

        Class use= null;

        try {

            use = Class.forName("com.example.User");

            //通过反射获取变量的@BindId注解

            for (Field f : use.getDeclaredFields()) {

                BindId bi=f.getAnnotation(BindId.class);

                if(bi!=null){

                    System.out.println(f.getName()+"......."+bi.value());

                }

            }

        } catch (ClassNotFoundException e) {

            e.printStackTrace();

        }

    }

}

  4.打印内容如下:   

viewId1.......101

viewId2.......102

viewId3.......103

viewId4.......104

 

Process finished with exit code 0

     到这时候,注解里的内容就获取到了,但是这种方式的缺点也显现出来了。是的,我们利用了反射,所以对性能会或多或少的产生影响。这也是大多数框架采用编译时注解的原因。

二.主题

     今天的主题就是编译时注解的处理,讲之前先要说明一下网上有很多关于注解处理器的文章,一搜AbstraceProcessor,哇好多文章。既然这样为什么还要写这篇博客记录呢?一是因为网上搜到文章大多比较早,很多都是17年以前的文章;再者就是文章太多了,是的,太多了,具体体验如下:

     我点开第一篇文章看到了如下配置:

     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

     额,没看懂,好吧,点开第二篇,这篇也许容易些,然后看到了如下配置:

    annotationProcessor 'com.google.dagger:dagger-compiler:2.0'

    咦,怎么这个还能和前面的不一样了?再打开一个,配置如下:

    compile'com.squareup:javapoet:1.8.0'

   compile'com.google.auto.service:auto-service:1.0-rc2'

    我。。。。。。WTF,这都是什么啊?就不能解释一下吗,该用什么。

    以上就是写这篇文章的原因。

首先了解以下几个概念

  一.什么是注解处理器?

注解处理器是(Annotation Processor)是javac的一个工具,用来在编译时扫描和编译和处理注解(Annotation)。你可以自己定义注解和注解处理器去搞一些事情。一个注解处理器它以Java代码或者(编译过的字节码)作为输入,生成文件(通常是java文件)。这些生成的java文件不能修改,并且会同其手动编写的java代码一样会被javac编译。看到这里加上之前理解,应该明白大概的过程了,就是把标记了注解的类,变量等作为输入内容,经过注解处理器处理,生成想要生成的java代码。

      对我来讲Annotation Process的实质用处就是在编译时通过注解获取相关数据

      处理器的写法有固定的套路,继承AbstractProcessor

二.什么是APT?

      APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

三.接下来开始实现一个编译处理器,具体步骤

    1.自定义注解

    2.注解处理器

    3.处理器注册

    其中1不用多说,从2说起:

    上面提到了处理器有固定的套路,我们处理注解,只需要继承AbstractProcessor

package com.example;

@SupportedAnnotationTypes("com.example.BindId")//我们要处理的注解

public class MyProcessor extends AbstractProcessor { 

       @Override 

        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){

//这里我们可以把得到的注解的内容进行编辑(打印或者编辑成java文件),关于参数自己查看  

return true; 

} 

}

恩,大概就是这样,流程还是需要简单些才明了。处理器定义完了,需要注册一下,步骤3:

 在resources资源文件夹下新建META-INF/services/javax.annotation.processing.Processor(这个是固定的,以文件形式创建),目录结构如下:

├─MyProcessor

│  │  

│  └─src

│      └─main

│          ├─java

│          │  └─com

│          │      └─example

│          │              MyProcessor.java

│          │              TestAnnotation.java

│          │              

│          └─resources

│              └─META-INF

│                  └─services

│                          javax.annotation.processing.Processor

创建好后,把我们2中定义的处理器添加进去即可:

com.example.MyProcessor

com.example.MyProcessor1

com.example.MyProcessor2

(.......有多少添加多少)

    步骤只有这些,不要多想,接下来看一个例子:

BindId.java   

@Retention(RetentionPolicy.CLASS)

@Target(ElementType.FIELD)

public @interface BindId {

    int value();

}

MyProcessor.java

@SupportedSourceVersion(SourceVersion.RELEASE_7)

@SupportedAnnotationTypes("com.example.annotationdemo.BindId")

public class MyProcessor extends AbstractProcessor {

    private Messager mMessager;

    @Override

    public synchronized void init(ProcessingEnvironment processingEnvironment) {

        super.init(processingEnvironment);

        mMessager = processingEnvironment.getMessager();

    }

 

    @Override

    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        Set<? extends Element> bindIdElements = roundEnvironment.getElementsAnnotatedWith(BindId.class);

        for (Element element : bindIdElements) {

            //1.获取注解的成员变量名

            VariableElement bindViewElement = (VariableElement) element;

            String bindViewFiledName = bindViewElement.getSimpleName().toString();

            //2.获取注解元数据

            BindId bindView = element.getAnnotation(BindId.class);

            int id = bindView.value();

            note(String.format("成员变量名 %s = 注解元数据 %d", bindViewFiledName, id));

        }

        return true;

    }

 

    private void note(String msg) {

        mMessager.printMessage(Diagnostic.Kind.NOTE, msg);

    }

}

这里说明一下,为了把过程简单话,这里只是在process中简单的打印一下注解内容,只要有了数据做什么都容易了,不管是写java文件也好,class文件也好,自己在这里编辑就是了,对吧?

以上就是对注解处理器的处理了。

四.使用

      到了这里,我们上面介绍的东西还有个没用上的APT。他的作用就体现在使用上。我们新建一个Module,然后就像使用butterknife 一样使用我们定义的注解处理器,使用如下:

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @BindId(0x0000)

    TextView t1;

    @BindId(0x0001)

    TextView t2;

    @BindId(0x0002)

    TextView t3;

    @BindId(0x0003)

    TextView t4;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }

 

}

我们自定义的注解处理器,写了这么多总要用啊,so打开我们module 的build.gradle

    annotationProcessor 'com.google.dagger:dagger-compiler:2.0'//android自带的APT工具

    implementation project(path: ':annotationdemo')  //依赖我们上面写的的项目

这时候rebuild project通过Gradle console打印如下:

注: 成员变量名 t1 = 注解元数据 0

注: 成员变量名 t2 = 注解元数据 1

注: 成员变量名 t3 = 注解元数据 2

注: 成员变量名 t4 = 注解元数据 3

至此,我们注解的内容就获取到了

五.扩展

 一.关于process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 这个方法

我们上面只用到了roundEnvironment这个参数,关于TypeElement这简单介绍一下。

@SupportedAnnotationTypes("com.example.annotationdemo.BindId")

public class MyProcessor extends AbstractProcessor {

}

关于这里这个@SupportedAnnotationTypes,它的value实际上是一个String数组,如图:

也就是说我们在一个注解处理器中可以处理多个自定义的注解,例如:

@SupportedAnnotationTypes({"com.example.annotationdemo.BindId""com.example.annotationdemo.BindId1""com.example.annotationdemo.BindId2"......})

StringBuilder sb=new StringBuilder();

        for(TypeElement typeElement :set){

            sb.append(typeElement.getSimpleName()+"--------");

            sb.append(typeElement.getQualifiedName()+"--------");

        }

        note(sb.toString());

将上面代码添加到process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 这个方法打印一下可以看到:

注: BindId--------com.example.annotationdemo.BindId--------BindId1--------com.example.annotationdemo.BindId1--------......

也就是我们只要通过roundEnvironment.getElementsAnnotatedWith(typeElement);就可以获取使用该注解的所有元素集合

2020-08-08 16:18:32

共有0条评论!

发表评论