这几天在学习自定义注解器,好记性不如烂笔头,将相关学习作为笔记记下来,加深理解,同时也进一步探索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