ButterKnife(三): ButterKnifeProcessor解析

前面已经讲到,ButterKnife使用了ButterKnifeProcessor注解处理器实现了编译时自动生成中间类,完成了findViewById操作,那么它是如何生成的呢? 首先我们需要了解 的相关基础知识了.

一.注解:

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
Java 内置定义了一套注解,比如我们常见的@Override,@Deprecated,@SuppressWarnings等,此处不展开讲解. ButterKnife使用时就是自定义了自己的一套注解,详细参考ButterKnife支持的注解
,然后使用自己的注解器解释并生成了相应的java类.

二,注解处理器

注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容.

有了这2项,那样就可以编译期间获取相关注解数据,然后动态生成.java源文件,解决了手工编写重复代码的问题.常用的工具,如: Dagger, EventBus都用到了注解相关技术.

三 AbstractProcessor 流程分析

AbstractProcessor 是扫描和处理注解的关键类,ButterKnife 自定义的 Processor 就需要继承自该类。下面,我们看看ButterKnifeProcessor的工作流程.

1. init()函数
 @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    String sdk = env.getOptions().get(OPTION_SDK_INT);
    if (sdk != null) {
      try {
        this.sdk = Integer.parseInt(sdk);
      } catch (NumberFormatException e) {
        env.getMessager()
            .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
                + sdk
                + "'. Falling back to API 1 support.");
      }
    }

    debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));

    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
      try {
        // Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
        for (Field field : processingEnv.getClass().getDeclaredFields()) {
          if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
            field.setAccessible(true);
            ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
            trees = Trees.instance(javacEnv);
            break;
          }
        }
      } catch (Throwable ignored2) {
      }
    }
  }

该方法主要是判断sdk及相关帮助类的初始化

2.getSupportedAnnotationTypes()
  @Override public Set getSupportedAnnotationTypes() {
    Set types = new LinkedHashSet<>();
    for (Class annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    return types;
  }

 private Set> getSupportedAnnotations() {
    Set> annotations = new LinkedHashSet<>();
    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }

要处理的注解类的名称集合,即 ButterKnife 支持的注解

3.process () //最主要的逻辑
@Override public boolean process(Set elements, RoundEnvironment env) {
// 查找并解析注解
    Map bindingMap = findAndParseTargets(env);

 // 去除注解的键值
    for (Map.Entry entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      // 生成java代码
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
      //写入文件
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

可以看到,process里面通过查找注解并写入了文件.

private Map findAndParseTargets(RoundEnvironment env) {
       Map builderMap = new LinkedHashMap<>();
       Set erasedTargetNames = new LinkedHashSet<>();

       scanForRClasses(env);
       ......//省略其余代码
       ......
       // env.getElementsAnnotatedWith(BindView.class)获取所有使用BindView注解的元素
       for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
           try {
               parseBindView(element, builderMap, erasedTargetNames);
           } catch (Exception e) {
               logParsingError(element, BindView.class, e);
           }
       }
       ......
       ......
       // 将builderMap中的数据添加到队列中
       Deque> entries =
               new ArrayDeque<>(builderMap.entrySet());
       Map bindingMap = new LinkedHashMap<>();
       while (!entries.isEmpty()) {
           // 出队列
           Map.Entry entry = entries.removeFirst();

           TypeElement type = entry.getKey();
           BindingSet.Builder builder = entry.getValue();
           // 查找当前类元素的父类元素
           TypeElement parentType = findParentType(type, erasedTargetNames);
           // 如果没找到则保存TypeElement和对应BindingSet
           if (parentType == null) {
               bindingMap.put(type, builder.build());
           } else {
               BindingSet parentBinding = bindingMap.get(parentType);
               if (parentBinding != null) {
                   // 如果找到父类元素,则给当前类元素对应的BindingSet.Builder设置父BindingSet
                   builder.setParent(parentBinding);
                   bindingMap.put(type, builder.build());
               } else {
                   // 再次入队列
                   entries.addLast(entry);
               }
           }
       }
       return bindingMap;
   }

先将扫描得到的注解相关信息保存到builderMap和erasedTargetNames中,最后对这些信息进行重新整理返回一个以TypeElement为 key 、BindingSet为 value 的 Map,其中TypeElement代表使用了 ButterKnife 的类,即 Activity、Fragment等,BindingSet是butterknife-compiler中的一个自定义类,用来存储要生成类的基本信息以及注解元素的相关信息

parseBindView()方法用来解析使用了BindView注解的元素:

private void parseBindView(Element element, Map builderMap,
                               Set erasedTargetNames) {
        // 首先要注意,此时element是VariableElement类型的,即成员变量
        // enclosingElement是当前元素的父类元素,一般就是我们使用ButteKnife时定义的View类型成员变量所在的类,可以理解为之前例子中的MainActivity
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

        // 进行相关校验
        // 1、isInaccessibleViaGeneratedCode(),先判断当前元素的是否是private或static类型,
        // 再判断其父元素是否是一个类以及是否是private类型。
        // 2、isBindingInWrongPackage(),是否在系统相关的类中使用了ButteKnife注解
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
                || isBindingInWrongPackage(BindView.class, element);

        // TypeMirror表示Java编程语言中的一种类型。 类型包括基元类型,声明的类型(类和接口类型),数组类型,类型变量和空类型。 
        // 还表示了通配符类型参数,可执行文件的签名和返回类型,以及与包和关键字void相对应的伪类型。
        TypeMirror elementType = element.asType();
        // 如果当前元素是类的成员变量
        if (elementType.getKind() == TypeKind.TYPEVAR) {
            TypeVariable typeVariable = (TypeVariable) elementType;
            elementType = typeVariable.getUpperBound();
        }
        Name qualifiedName = enclosingElement.getQualifiedName();
        Name simpleName = element.getSimpleName();
        // 判断当前元素是否是 View 的子类,或者是接口,不是的话抛出异常
        if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
            if (elementType.getKind() == TypeKind.ERROR) {
                note(element, "@%s field with unresolved type (%s) "
                                + "must elsewhere be generated as a View or interface. (%s.%s)",
                        BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
            } else {
                error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
                        BindView.class.getSimpleName(), qualifiedName, simpleName);
                hasError = true;
            }
        }
        
        if (hasError) {
            return;
        }

        // 获得元素使用BindView注解时设置的属性值,即 View 对应的xml中的id
        int id = element.getAnnotation(BindView.class).value();
        // 尝试获取父元素对应的BindingSet.Builder
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        // QualifiedId记录了当前元素的包信息以及id
        QualifiedId qualifiedId = elementToQualifiedId(element, id);
        if (builder != null) {
            String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
            // 如果当前id已经被绑定,则抛出异常
            if (existingBindingName != null) {
                error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
                        BindView.class.getSimpleName(), id, existingBindingName,
                        enclosingElement.getQualifiedName(), element.getSimpleName());
                return;
            }
        } else {
            // 创建一个新的BindingSet.Builder并返回,并且以enclosingElement 为key添加到builderMap中
            builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }

        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        // 判断当前元素是否使用了Nullable注解
        boolean required = isFieldRequired(element);
        // 创建一个FieldViewBinding,它包含了元素名、类型、是否是Nullable
        // 然后和元素id一同添加到BindingSet.Builder
        builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

        // 记录当前元素的父类元素
        erasedTargetNames.add(enclosingElement);
    }

回到process()方法

       // 得到java类源码
        JavaFile javaFile = binding.brewJava(sdk, debuggable);
        try {
            // 生成java文件
            javaFile.writeTo(filer);
        } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
        }

这个过程用到了JavaPoet开源库,提供了一种友好的方式来辅助生成 java 类代码,同时将类代码生成文件,否则需要自己拼接字符串来实现,可以发现BindingSet除了保存信息目标类信息外,还封装了 JavaPoet 生成目标类代码的过程。关于JavaPoet的知识就不继续探究了,有兴趣的可以自己探索.

四. 自定义MyProcessor

那么我们如何自定义一个MyProcessor呢?

  1. create new module--->java or kotlin lib; //一定要是java lib ; 不能是android lib
  2. extends AbstractProcessor并复写它的几个方法


    new_module.png
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    // 在 Processor 创建时调用并执行的初始化操作
    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    // 关键方法,进行扫描和处理注解,并生成新的源代码
    @Override
    public boolean process(Set annoations, RoundEnvironment env) { }

    // 指定需要注册的注解
    @Override
    public Set getSupportedAnnotationTypes() { }
    
    // 指定支持的 Java 版本
    @Override
    public SourceVersion getSupportedSourceVersion() { }

}
  • @AutoService(Processor.class) :向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。
    AutoService这里主要是用来生成META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解,那么,你需要自己进行手动配置进行注册
  • init(ProcessingEnvironment env):每个Annotation Processor必须***
    有一个空的构造函数 *。编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnviroment参数,通过该参数可以获取到很多有用的工具类: Elements , Types , Filer **等等
  • process(Set annoations, RoundEnvironment roundEnv):Annotation Processor扫描出的结果会存储进roundEnv中,可以在这里获取到注解内容,编写你的操作逻辑。注意,process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃
  • getSupportedAnnotationTypes(): 该函数用于指定该自定义注解处理器(Annotation Processor)是注册给哪些注解的(Annotation),注解(Annotation)指定必须是完整的包名+类名(eg:com.example.MyAnnotation)
  • getSupportedSourceVersion():用于指定你的java版本,一般返回:SourceVersion.latestSupported()。当然,你也可以指定具体java版本:比如: return SourceVersion.RELEASE_7;

完整的demo就不跑了,详情请看注解处理器(Annotation Processor)简析

我的ButterKnife相关文章

ButterKnife(一): 使用篇
ButterKnife(二): 原理解析篇
ButterKnife(三): ButterKnifeProcessor解析

参考文献

ButterKnife 原理解析
注解处理器(Annotation Processor)简析

你可能感兴趣的:(ButterKnife(三): ButterKnifeProcessor解析)