android自定义编译注解器学习笔记(一)

            这几天在学习自定义注解器,好记性不如烂笔头,将相关学习作为笔记记下来,加深理解,同时也进一步探索butterknife的原理。


一.注解器的配置

            自定义编译注解器 主要是需要继承 AbstractProcessor这个类,这里有两点要注意:

         ①这个类是javax扩展包下的类,它不在android提供的类里,如果直接在android app module或者是android library module里去实现,是找不到这个类的,正确的做法是:新建一个java library 的module,在这个library即可正常使用。

          ②需要注册这个自定义的注解器,有两种方法可以注册,第一个是在这个java library 的gradle文件引进auto-service依赖库:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.auto.service:auto-service:1.0-rc2'
}

      然后在自己的注解处理器上面添加AutoService注解:注意AutoService注解值都是Processor.class,不然不会自动注册。关于auto-service这个注解的详细信息可以看这 https://github.com/google/auto/tree/master/service

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
}

            第二种方法是手动注册,在module的src/main/目录下创建一个文件夹resources,再在这个文件夹下创建一个文件夹META-INF,在META-INF这个文件夹下创建文件javax.annotation.processing.Processor,文件内容为自定义处理器的全路径,比如我的是com.example.myprocessor.MyProcessor.

            javax.annotation.processing.Processor文件内容写上两个自定义注解器:

com.example.myprocessor.MyProcessor
com.example.myprocessor.CodeProcessor
            有多少个注解器就写多少行。以上就是两种注册注解器的方法,推荐第一种,比较方便。接下来就让android的app module依赖这个库,就可以开始使用了。


二.注解器的element

     接下来重点复习下自定义注解处理器,毫无疑问,最重要的就是process()和getSupportedAnnotationTypes()这两个方法,一个决定怎么处理注解,一个决定哪些注解要被这个处理器处理。

public class MyProcessor extends AbstractProcessor{

    private Messager mMessager;

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //....
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set=new LinkedHashSet<>();
        set.add(Print.class.getCanonicalName());
        return set;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager=processingEnvironment.getMessager();
    }

}

          首先看process方法里面的Set<? extends TypeElement> set参数,这个参数的内容实际上是由getSupportedAnnotationTypes()这个方法返回的Set集合决定的,等会我们可以来打印一下。说到这个参数,就不得不重点讲解一下这个TypeElement和Element元素。


Element

        Element表示一个静态的,语言级别的构件。而任何一个结构化文档都可以看作是由不同的element组成的结构体,比如XML,html等 【1】。比如xml文档

<root>
    <child id=1>
        <subchild>...</subchild>
    </child>
</root>

          写过web前端的技术人员应该对这些再熟悉不过了。而java文件同样可以看做是一个结构化文档:

package com.example;             //PackageElement

public class Main{                  //TypeElement
    private int x;                  //VariableElement

    private Main(){                 //ExecuteableElement
    }
}
            这些结构化元素同样会形成一个“父子”关系,比如PackageElement可以看作是<root>标签,它有一个子元素TypeElement——Main这个类,而Main类下的所有方法和字段都是这个Main的子元素,这种结构关系使得java文件容易被解析,比如DOM解析方法。

           回到我们刚才说的, PackageElement,TypeElement这些元素统称为Element,在Java里都是设计成继承自Element。TypeElement用来代表类、接口以及注解元素。接下来具体演示一下:

              随便写一个注解:    

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Print {
}
              在MainActivity里给一个TextView使用:
public class MainActivity extends AppCompatActivity {
    //...

    @Print
    private  TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //...
    }
    //...
      然后是我们的注解器的process方法:逐一遍历set参数里面的注解元素,使用roundEnvironment.getElementsAnnotatedWith(et)这个方法来获得被注解的元素。
@Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        for(TypeElement et:set){
            mMessager.printMessage(Diagnostic.Kind.NOTE,"TypeElement:"+et.toString());
            for(Element e:roundEnvironment.getElementsAnnotatedWith(et)){
                mMessager.printMessage(Diagnostic.Kind.NOTE,"Element:"+e.toString());

                //getEnclosingElement方法是获得父元素
                TypeElement clazz = (TypeElement) e.getEnclosingElement();
                mMessager.printMessage(Diagnostic.Kind.NOTE,"clazz :"+clazz.toString()+",type:"+clazz.getKind());

                Element pkg= clazz.getEnclosingElement();
                mMessager.printMessage(Diagnostic.Kind.NOTE,"pkg :"+pkg.toString()+",type:"+pkg.getKind());

                //getEnclosedElements方法是获得子元素
                List<? extends Element> enclosedElements = clazz.getEnclosedElements();
                for(Element subEt:enclosedElements){
                    mMessager.printMessage(Diagnostic.Kind.NOTE,"enclosedElements :"+subEt+",type:"+subEt.getKind());
                }
            }
        }
        return true;
    }
                 最终在gradle的输出台会输出以下信息,这里只是打印了一下,实际开发过程中可以使用类似element.getKind() == ElementKind.CLASS 的方式来判断element元素的类型。


            roundEnvironment是已经完成了注解元素的解析,通过它可以获得被注解的元素。mMessage专门用来编译时期的输出信息,就像android的logcat一样,但这里要注意的是不能随便把printMessage的第一个参数的信息类型设置为Kind.ERROR,不然编译不过,log.e和system.err不会,编程还是规范点好。

          通过上面的输出信息我们可以看到,通过一个注解便可以得到整个java文件的结构元素信息,剩下的便可以根据这些元素来进行下一步的操作。

 

TypeMirror:

        Element代表语言元素,比如包,类,接口等,但是Element并没有包含自身的信息,自身信息要通过Mirror来获取。这句话应该怎么来理解呢,这里举一个例子来说明:

<root>
    <child id=1>
        <subchild >com.example.Demo</subchild>
    </child>
</root>

       看上面这个结构文档,标签<subchild>里面的内容是“com.example.Demo”,我们拿到<subchild>这个element的时候,我们可以获取到这个element的内容“com.example.Demo”,除此之外好像就没有了,换句话说,element只知道有这个字符串“com.example.Demo”,但不知道这个字符串代表的是什么东西,代表的是类呢?还是包呢?还是方法呢?还是字段呢?这是element这个对象并不知道也不会关心的事情,所以开发者就设计了TypeMirror这个对象来解释这个信息,这个设计采用的是Mirror设计思想。每个Element都指向一个TypeMirror,这个TypeMirror里面就包含了自身的信息。可以通过下面方面获取Element中的Mirror:

TypeMirror mirror=element.asType() 

          通过这个TyepMirror可以获得诸如自己父类或者接口的信息,这个对象是非常重要的,我们拿一段butterknife的源码来分析,在butterknife中,有个parseBindView,目的是为了解析使用了BindView.class这个注解的element。

    //这个element就是使用了BindView.class注解,比如textView
    TypeMirror elementType = element.asType();
    // TypeVar指的是变量variable类型
    if (elementType.getKind() == TypeKind.TYPEVAR) {   
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }

           既然是使用BindView注解,那要判断被注解的对象是否是继承了View对象吧?所以有了如下的判断:

    //这个VIEW_TYPE是View的包名,VIEW_TYPE="android.view.View"
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      //...
    }

           接着我们来看下isSubtypeOfType的源码:

static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
    //isTypeEqual方法很简单,就一句话otherType.equals(typeMirror.toString()),假如是TextView的话,
    //这里的typeMirror.toString()返回的是"android.widget.TextView",所以一般第一次这里是返回false,
    if (isTypeEqual(typeMirror, otherType)) {
      return true;
    }
    //判断是否是声明类型
    if (typeMirror.getKind() != TypeKind.DECLARED) {
      return false;
    }
    //这一步强转很重要的,上面那一段代码的element变量你打印出来只是个变量名,比如是个TextView的话那么名字是"tv"
    //相当于只是知道了它的名字,并不清楚它是什么东西,element.asType().toString()返回会是"android.widget.TextView"
    //到这一步应该更能理解typeMirror和element的关系了吧?哈哈。
    DeclaredType declaredType = (DeclaredType) typeMirror;
    //声明类型如果是方法的话,获得它的参数列表的typeMirror,如果不是方法,比如是textView的话这里返回的集合是空的。
    List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
    if (typeArguments.size() > 0) {
      StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
      typeString.append('<');
      for (int i = 0; i < typeArguments.size(); i++) {
        if (i > 0) {
          typeString.append(',');
        }
        typeString.append('?');
      }
      typeString.append('>');
      if (typeString.toString().equals(otherType)) {
        return true;
      }
    }
    //这里又把这个typeMirror转成element,注意这个转换不是随意的,必须是DeclaredType或者是TypeVariable
    Element element = declaredType.asElement();
    if (!(element instanceof TypeElement)) {
      return false;
    }
    TypeElement typeElement = (TypeElement) element;
    //如果是textView的话,这里getSuperclass返回的就是"android.view.View"了,这样继续进行递归判断
    TypeMirror superType = typeElement.getSuperclass();
    if (isSubtypeOfType(superType, otherType)) {
      return true;
    }
    //如果父类没有找到一样的typeMirror,则会去寻找它的所有实现接口继续去判断。
    for (TypeMirror interfaceType : typeElement.getInterfaces()) {
      if (isSubtypeOfType(interfaceType, otherType)) {
        return true;
      }
    }
    return false;
  }

这一篇就先到这里。





参考博客:

【1】https://blog.csdn.net/dd864140130/article/details/53875814

【2】https://blog.csdn.net/lmj623565791/article/details/43452969

猜你喜欢

转载自blog.csdn.net/aa642531/article/details/80219214