美文网首页
ButterKnife(三): ButterKnifeProce

ButterKnife(三): ButterKnifeProce

作者: small_tadpole | 来源:发表于2020-03-09 14:42 被阅读0次

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

    一.注解:

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

    二,注解处理器

    注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码\color{red}{编译}阶段,通过注解处理器,我们可以获取源文件内注解(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<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
          types.add(annotation.getCanonicalName());
        }
        return types;
      }
    
     private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> 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<? extends TypeElement> elements, RoundEnvironment env) {
    // 查找并解析注解
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    
     // 去除注解的键值
        for (Map.Entry<TypeElement, BindingSet> 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<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
           Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
           Set<TypeElement> 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<Map.Entry<TypeElement, BindingSet.Builder>> entries =
                   new ArrayDeque<>(builderMap.entrySet());
           Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
           while (!entries.isEmpty()) {
               // 出队列
               Map.Entry<TypeElement, BindingSet.Builder> 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<TypeElement, BindingSet.Builder> builderMap,
                                   Set<TypeElement> 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<? extends TypeElement> annoations, RoundEnvironment env) { }
    
        // 指定需要注册的注解
        @Override
        public Set<String> 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<? extends TypeElement> 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(三): ButterKnifeProce

          本文链接:https://www.haomeiwen.com/subject/lhkudhtx.html