• IOC架构设计之ButterKnife源码&原理(二)上篇


    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

    一、代码结构

     
    19956127-7a3f85f83b55b659.png
     

    我们这里对ButterKnife的最新版本8.4.0进行分析。
    我们先down下来看下代码的结构,可以看到代码结构分的还是很好的。

    • butterknife ;android library model 提供android使用的API
    • butterknife-annotations; java-model,使用时的注解
    • butterknife-compiler;java-model,编译时用到的注解的处理器
    • butterknife-gradle-plugin;自定义的gradle插件,辅助生成有关代码
    • butterknife-integration-test;该项目的测试用例
    • butterknife-lint;该项目的lint检查
    • sample;demo
      这里重点分析butterknife-compiler及butterknife

    二、原理图

    建议看完全篇了再返回看这个图,会更明白。

     
    19956127-910d9c0b2aeaa340.png
     
     
    19956127-7fee848b51b93a0d.png
     

    三、使用方法

    这里不再给出。不过会在原代码分析的时候给出一些注意的地方。
    我们拿官方的demo-SimpleActivity
    编译完后最后生成的文件为:SimpleActivity_ViewBinding
    路径在:

     
    19956127-3072c519b94e28b6.png
     


    内容:

    // Generated code from Butter Knife. Do not modify!
    package com.example.butterknife.library;
    
    import android.support.annotation.CallSuper;
    import android.support.annotation.UiThread;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.Button;
    import android.widget.ListView;
    import android.widget.TextView;
    import butterknife.Unbinder;
    import butterknife.internal.DebouncingOnClickListener;
    import butterknife.internal.Utils;
    import java.lang.IllegalStateException;
    import java.lang.Override;
    
    public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
      protected T target;
    
      private View view2130968578;
    
      private View view2130968579;
    
      @UiThread
      public SimpleActivity_ViewBinding(final T target, View source) {
        this.target = target;
    
        View view;
        target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
        target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
        view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
        target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
        view2130968578 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.sayHello();
          }
        });
        view.setOnLongClickListener(new View.OnLongClickListener() {
          @Override
          public boolean onLongClick(View p0) {
            return target.sayGetOffMe();
          }
        });
        view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
        target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
        view2130968579 = view;
        ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
          @Override
          public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
            target.onItemClick(p2);
          }
        });
        target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
        target.headerViews = Utils.listOf(
            Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), 
            Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
            Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
      }
    
      @Override
      @CallSuper
      public void unbind() {
        T target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
    
        target.title = null;
        target.subtitle = null;
        target.hello = null;
        target.listOfThings = null;
        target.footer = null;
        target.headerViews = null;
    
        view2130968578.setOnClickListener(null);
        view2130968578.setOnLongClickListener(null);
        view2130968578 = null;
        ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
        view2130968579 = null;
    
        this.target = null;
      }
    }
    

    我们看到了我们熟悉的代码,虽然比较乱(因为是生成的),
    可以看出 在构造中findview 在unbind中进行置null处理,让告诉gc在合适的机会回收占用的内存 ;但是这是后面真正生成代码我们看不到的,没关系 嘻嘻。

    3.1 整体的原理-(编译时期-注解处理器)

    在java代码的编译时期,javac 会调用java注解处理器来进行处理。因此我们可以定义自己的注解处理器来干一些事情。一个特定注解的处理器以 java 源代码(或者已编译的字节码)作为输入,然后生成一些文件(通常是.java文件)作为输出。因此我们可以在用户已有的代码上添加一些方法,来帮我们做一些有用的事情。这些生成的 java 文件跟其他手动编写的 java 源代码一样,将会被 javac 编译。(个人参考及个人理解)

    3.2 定义处理器 继承AbstractProcessor

    在java中定义自己的处理器都是继承自AbstractProcessor
    前3个方法都试固定写法,主要是process方法。

    public class MyProcessor extends AbstractProcessor {
    
        //用来指定你使用的 java 版本。通常你应该返回                                SourceVersion.latestSupported()
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        //会被处理器调用,可以在这里获取Filer,Elements,Messager等辅助类,后面会解释
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
        }
    
    
    //这个方法返回stirng类型的set集合,集合里包含了你需要处理的注解
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> annotataions = new LinkedHashSet<String>();
            annotataions.add("com.example.MyAnnotation");
            return annotataions;
        }
    
    
       //核心方法,这个一般的流程就是先扫描查找注解,再生成 java 文件
       //这2个步骤设计的知识点细节很多。
    
        @Override
        public boolean process(Set<? extends TypeElement> annoations,
                RoundEnvironment env) {
            return false;
        }
    }
    

    3.3 注册你的处理器

    要像jvm调用你写的处理器,你必须先注册,让他知道。怎么让它知道呢,其实很简单,google 为我们提供了一个库,简单的一个注解就可以。
    首先是依赖

    compile 'com.google.auto.service:auto-service:1.0-rc2'
    
    @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {
      //...省略非关键代码
    }
    

    3.4 基本概念

    Elements:一个用来处理Element的工具类
    Types:一个用来处理TypeMirror的工具类
    Filer:你可以使用这个类来创建.java文件

    四、源码分析

    分析之前呢先要有写基本的概念

    可以看到AutoService注解

    @AutoService(Processor.class)
    public final class ButterKnifeProcessor extends AbstractProcessor {
    //...
    }
    

    (1)init 方法,这个主要是获取一些辅助类

    private Filer mFiler; //文件相关的辅助类
       private Elements mElementUtils; //元素相关的辅助类
       private Messager mMessager; //日志相关的辅助类
    
     @Override public synchronized void init(ProcessingEnvironment env) {
       super.init(env);
    
       elementUtils = env.getElementUtils();
       typeUtils = env.getTypeUtils();
       filer = env.getFiler();
       try {
         trees = Trees.instance(processingEnv);
       } catch (IllegalArgumentException ignored) {
       }
     }
    

    (2)getSupportedSourceVersion()方法就是默认的,获取你该处理器使用的java版本

     @Override public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
      }
    

    (3)接下来就是process方法,这个方法是核心方法。

     @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
     //1.查找所有的注解信息,并形成BindingClass(是什么 后面会讲) 保存到 map中
        Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    
    //2.遍历步骤1的map 的生成.java文件也就是上文的  类名_ViewBinding  的java文件
    
      for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingClass bindingClass = entry.getValue();
    
          JavaFile javaFile = bindingClass.brewJava();
          try {
            javaFile.writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }
    
        return true;
      }
    

    显然分两步执行,下面再仔细走一下该方法流程。

    每个注解的查找与解析-findAndParseTargets

      private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
        scanForRClasses(env);
        //...下面是每个注解的解析
     // Process each @BindArray element.
        for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
            parseResourceArray(element, targetClassMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindArray.class, e);
          }
        }
    
         // Process each @BindView element.
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
          // we don't SuperficialValidation.validateElement(element)
          // so that an unresolved View type can be generated by later processing rounds
          try {
            parseBindView(element, targetClassMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindView.class, e);
          }
        }
       //....
    
    
          // Process each annotation that corresponds to a listener.
        for (Class<? extends Annotation> listener : LISTENERS) {
          findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
        }
    
        // Try to find a parent binder for each.
        for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
          if (parentType != null) {
            BindingClass bindingClass = entry.getValue();
            BindingClass parentBindingClass = targetClassMap.get(parentType);
            bindingClass.setParent(parentBindingClass);
          }
        }
    
        return targetClassMap;
    
    }
    

    第一步
    我们先看一下参数 RoundEnvironment 这个是什么呢?个人理解是注解框架里的一个工具什么工具呢?一个可以在处理器处理该处理器 用来查询注解信息的工具,当然包含你在getSupportedAnnotationTypes注册的注解。
    第二步
    创建了一个LinkedHashMap保证了先后的顺序就是先注解的先生成java文件,其实也没有什么先后无所谓。最后将其返回。
    我们这里只分析一个具有代表性的BindView注解,其它的都是一样的,连代码都一毛一样。
    在这之前先看一下Element这个类我们看一下官方注释

     * Represents a program element such as a package, class, or method.
     * Each element represents a static, language-level construct
     * (and not, for example, a runtime construct of the virtual machine).
    

    在注解处理器中,我们扫描 java 源文件,源代码中的每一部分都是Element的一个特定类型。换句话说:Element代表程序中的元素,比如说 包,类,方法。每一个元素代表一个静态的,语言级别的结构.
    比如:

    public class ClassA { // TypeElement
        private int var_0; // VariableElement
        public ClassA() {} // ExecuteableElement
    
        public void setA( // ExecuteableElement
                int newA // TypeElement
        ) {
        }
    }
    

    可以看到类为TypeElement,变量为VariableElement,方法为ExecuteableElement
    这些都是Element的子类,核对源码。

    TypeElement aClass ;
    for (Element e : aClass.getEnclosedElements()){ //获取所有的子节点
        Element parent = e.getEnclosingElement();  // 获取父节点
        }
    

    Elements代表源代码,TypeElement代表源代码中的元素类型,例如类。然后,TypeElement并不包含类的相关信息。你可以从TypeElement获取类的名称,但你不能获取类的信息,比如说父类。这些信息可以通过TypeMirror获取。你可以通过调用element.asType()来获取一个Element的TypeMirror。

    这个是对于理解源码的基础。
    继续,我们看到了for循环查找所有包含BindView的注解。

     parseBindView(element, targetClassMap, erasedTargetNames);
    

    把element和targetClassMap传入

      private void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,
          Set<TypeElement> erasedTargetNames) {
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
         //1.检查用户使用的合法性
        // Start by verifying common generated code restrictions.
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
            || isBindingInWrongPackage(BindView.class, element);
    
        // Verify that the target type extends from View.
        TypeMirror elementType = element.asType();
        if (elementType.getKind() == TypeKind.TYPEVAR) {
          TypeVariable typeVariable = (TypeVariable) elementType;
          elementType = typeVariable.getUpperBound();
        }
        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, enclosingElement.getQualifiedName(),
                element.getSimpleName());
          } else {
            error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
                BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
                element.getSimpleName());
            hasError = true;
          }
        }
    
        // 不合法的直接返回
        if (hasError) {
          return;
        }
    
    
        //2.获取id 值
    
        // Assemble information on the field.
        int id = element.getAnnotation(BindView.class).value();
    
        // 3.获取 BindingClass,有缓存机制, 没有则创建,下文会仔细分析
    
        BindingClass bindingClass = targetClassMap.get(enclosingElement);
        if (bindingClass != null) {
          ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));
          if (viewBindings != null && viewBindings.getFieldBinding() != null) {
            FieldViewBinding existingBinding = viewBindings.getFieldBinding();
            error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
                BindView.class.getSimpleName(), id, existingBinding.getName(),
                enclosingElement.getQualifiedName(), element.getSimpleName());
            return;
          }
        } else {
          bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
        }
       //4 生成FieldViewBinding 实体 
    
        String name = element.getSimpleName().toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
    
        FieldViewBinding binding = new FieldViewBinding(name, type, required);
         //5。加入到 bindingClass 成员变量的集合中
        bindingClass.addField(getId(id), binding);
    
        // Add the type-erased version to the valid binding         targets set.
        erasedTargetNames.add(enclosingElement);
    
      }
    

    element.getEnclosingElement();是什么呢?是父节点。就是上面我们说的。
    基本的步骤就是上面的5步
    1.检查用户使用的合法性

       // Start by verifying common generated code restrictions.
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
            || isBindingInWrongPackage(BindView.class, element);
    
     private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
          String targetThing, Element element) {
        boolean hasError = false;
        // 得到父节点
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
     //判断修饰符,如果包含private or static 就会抛出异常。
        // Verify method modifiers.
        Set<Modifier> modifiers = element.getModifiers();
        if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
          error(element, "@%s %s must not be private or static. (%s.%s)",
              annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
    
            //判断父节点是否是类类型的,不是的话就会抛出异常
            //也就是说BindView 的使用必须在一个类里
        // Verify containing type.
        if (enclosingElement.getKind() != CLASS) {
          error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
              annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
    
         //判断父节点如果是private 类,则抛出异常
        // Verify containing class visibility is not private.
        if (enclosingElement.getModifiers().contains(PRIVATE)) {
          error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
              annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
    
        return hasError;
      }
    

    上面的代码里遇见注释了,这里说一下也就是我们在使用bindview注解的时候不能用使用

     类不能是private修饰 ,可以是默认的或者public 
      //in adapter
       private  static final class ViewHolder {
       //....
       }
       //成员变量不能是private修饰 ,可以是默认的或者public 
       @BindView(R.id.word)
      private  TextView word;  
    

    接下来还有一个方法isBindingInWrongPackage。
    这个看名字也才出来个大概 就是不能在android ,java这种源码的sdk中使用。如果你的包名是以android或者java开头就会抛出异常。

       private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
          Element element) {
          //得到父节点
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
        String qualifiedName = enclosingElement.getQualifiedName().toString();
    
        if (qualifiedName.startsWith("android.")) {
          error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
              annotationClass.getSimpleName(), qualifiedName);
          return true;
        }
        if (qualifiedName.startsWith("java.")) {
          error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
              annotationClass.getSimpleName(), qualifiedName);
          return true;
        }
    
        return false;
      } 
    

    到这里合法性检查就完了,如果你使用不当,就会抛出异常。

     // 。。。 异常抛出了
     // 不合法的直接返回
        if (hasError) {
          return;
        }
    

    这里说明一点,在处理器中抛出异常,你不能直接像平常写java代码一样new thow xxx 一样,这样抛出去的异常不太好看。所以java处理器帮我们提供了一个辅助类Messager,这个可以帮助我们

          /**
         * Returns the messager used to report errors, warnings, and other
         * notices.
         *
         * @return the messager
         */
        Messager getMessager();
    

    比如检查使用合法性抛出的异常信息-error方法最后都会调用

      private void error(Element element, String message, Object... args) {
      //Kind.ERROR 级别,就像你使用android的log一样
        printMessage(Kind.ERROR, element, message, args);
      }
    
      private void note(Element element, String message, Object... args) {
        printMessage(Kind.NOTE, element, message, args);
      }
    
      private void printMessage(Kind kind, Element element, String message, Object[] args) {
        if (args.length > 0) {
          message = String.format(message, args);
        }
    
        processingEnv.getMessager().printMessage(kind, message, element);
    }
    

    至此完成了检查。
    原文链接https://blog.csdn.net/ta893115871/article/details/52497297

  • 相关阅读:
    初识C++
    Linux下死锁的调研
    C语言实现单链表面试题(进阶篇)
    C语言实现单链表面试题(基础篇)
    IPC之—共享内存
    IPC之—信号量
    IPC之—消息队列
    初识多线程
    Mysql5.7安装
    RabbitMQ单节点安装/使用!
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/12103038.html
Copyright © 2020-2023  润新知