美文网首页
学习笔记ButterKnife

学习笔记ButterKnife

作者: 回眸婉约 | 来源:发表于2021-01-05 17:08 被阅读0次

    深度学一下Butterknife是如何帮我们自动生成findViewById的
    第一行

    ButterKnife.bind(this);
    

    点开

    public static Unbinder bind(@NonNull Activity target) {
      View sourceView = target.getWindow().getDecorView();
      return bind(target, sourceView);
    }
    

    获取 DecorViewDecorView 包含setContentView 的xml和 ActionBarDecorView 又被 PhoneWindow 包含。

    @NonNull @UiThread
      public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        //获取Class对象
        Class<?> targetClass = target.getClass();
        if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
        //获取constructor对象
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    
        if (constructor == null) {
          return Unbinder.EMPTY;
        }
    
        try {
          //返回生成的对象
          return constructor.newInstance(target, source);
        } catch (IllegalAccessException e) {
          throw new RuntimeException("Unable to invoke " + constructor, e);
        } //去掉了部分catch
      }
    

    Class类:在java中,每个定义的类都会有一个相应的 Class 。在编译完成后生成在 .class 文件中,它是一个类,在long包中,私有构造函数,像基本类型、数组、void也都有一个 Class 是用来表示这个类的类型信息的。Java在运行时,系统会对所有对象进行 运行时类型标识,虚拟机运行时会以准确的方法执行。
    Class:获取

    1. 获取Class.forName(“类的全限定名”)
    2. 实例对象.getClass()
    3. 类名.class (类字面常量)
      使用
    4. Class.forName("类名")装入类,只做静态初始化,返回Class
    5. newInstance()调用无惨构造函数返回实例
    6. isInstance(..)判断里面的对象是否是子类
    7. isInterface(..)判断接口
    8. getSimpleName()获取对象类名等等,都是和当前类相关的内容
      类名.classClass 类的区别:
      类名.class是 JVM将类装入内存 (还未装入内存),不做初始化工作,返回Class对象。只能用类名点出class,Button.class
      Constructor(构造器)类:提供有关类 的构造函数,比如String.class未被装入内存,可以直接获取他的构造函数
    Constructor<?>[] c = String.class.getConstructors();
    

    之后可以通过对象 c 找到需要的构造函数进行 newInstance(..) 实例化

    findBindingConstructorForClass()
     static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
    
    @Nullable @CheckResult @UiThread
     private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
       //这里做了一个缓存,如果缓存有直接返回
       Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
       if (bindingCtor != null || BINDINGS.containsKey(cls)) {
         if (debug) Log.d(TAG, "HIT: Cached in binding map.");
         return bindingCtor;
       }
       //获取类名
       String clsName = cls.getName();
       if (clsName.startsWith("android.") || clsName.startsWith("java.")
           || clsName.startsWith("androidx.")) {
         if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
         return null;
       }
       try {
         //根据 类名_ViewBinding  返回Class对象  
         Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
         //根据Class对象  返回 构造函数类 对象
         bindingCtor = (Constructor<? extends Unbinder>)bindingClass.getConstructor(cls, View.class);
         if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
       } catch (ClassNotFoundException e) {
         if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
         bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
       } catch (NoSuchMethodException e) {
         throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
       }
       //放入缓存,便于重用
       BINDINGS.put(cls, bindingCtor);
       return bindingCtor;
     }
    

    根据类名_ViewBinding返回Class对象,比如你的一个界面叫 MainActivity ,注解生成器会生成一个类文件叫 MainActivity_ViewBinding。
    ButterKnife类主要功能就是,运行注解处理器 生成出来的文件。
    关键还是在于ButterKnife的注解处理类

    ButterKnifeProcessor extends AbstractProcessor

    Java注解处理器,简单来说就是在编译时,扫描所有文件,收集带有注解的变量、类方法等等统一到一块,主要是 编译时

    public final class ButterKnifeProcessor extends AbstractProcessor {
      //变量名
      //定义了一些字符串 用private static final String修饰
      //定义了List放的是一些 事件 的 注解集合
      @Override public synchronized void init(ProcessingEnvironment env) {
          //初始化一些内容
      }
    
      //支持的注解类型 意思就是 添加了 才能用
      @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;
      }
      //生成代码的部分
      @Override public boolean process(Set<? extends TypeElement> elements,   RoundEnvironment env) {
        //寻找解析注解
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
        //遍历上面的map 循环的生成文件
        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue();
          //生成JavaFile对象
          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;
        }
    }
    

    bindingMap:寻找注解,以Key-value的方式存储
    key:TypeElement类型元素,大概包含包名、类名、文件路径等等,我理解为“类信息”
    value:BindingSet是一个所有绑定的集合,也就是 “类” 里面的有 需要绑定的集合
    下面是里面代码

    1、findAndParseTargets(env)
    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        //这个不是最后返回的对象,这个只存了BindAnim、BindString、BindView等等的数据
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
        //隐藏代码@BindAnim
        //影藏很多 bind
        //隐藏代码@BindString
    
        //绑定  界面上的View
        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, builderMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindView.class, e);
          }
        }
        //隐藏代码@BindViews
    
        //隐藏代码 bindlistener.
    
    
        Map<TypeElement, ClasspathBindingSet> classpathBindings =
            findAllSupertypeBindings(builderMap, erasedTargetNames);
    
        //使用队列的方式,绑定superClass
        // Associate superclass binders with their subclass binders. This is a queue-based tree walk
        // which starts at the roots (superclasses) and walks to the leafs (subclasses).
        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, classpathBindings.keySet());
          if (parentType == null) {
            bindingMap.put(type, builder.build());
          } else {
            BindingInformationProvider parentBinding = bindingMap.get(parentType);
            if (parentBinding == null) {
              parentBinding = classpathBindings.get(parentType);
            }
            if (parentBinding != null) {
              builder.setParent(parentBinding);
              bindingMap.put(type, builder.build());
            } else {
              // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
              entries.addLast(entry);
            }
          }
        }
    
        return bindingMap;
      }
    

    这里分2块内容
    第一部分是查找,BindString、BindView等等 的类
    第二部分是把所有的类进行联合变成

    第一部分
    //关键是这里BindView, 先不看BindString,BindAnim等等 都是相通的
    parseBindView(element, builderMap, erasedTargetNames);
    
    //直接贴代码
    private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
        Set<TypeElement> erasedTargetNames) {
      TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
      //校验类型
      //方法名意思是:生成后的代码 无法访问一些内容,比如private等等
      //isBindingInWrongPackage:绑定的内容 在错误的包
      boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
          || isBindingInWrongPackage(BindView.class, element);
    
      // 验证类型是否从View扩展,就是是否是View的子类
      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;
      }
    
      //开始收集所有信息
      //获取id
      int id = element.getAnnotation(BindView.class).value();
      //获取一个builder,这个builder是从builderMap中取的,这个builderMap是传进来的
      //参数enclosingElement 是这样赋值的 (TypeElement) element.getEnclosingElement();
      //这个TypeElement  我之前写了是 理解为 “类信息” 的
      //所以是用get方法取, 因为第一个绑定方法是BindAnim,有可能这里已经 创建过了
      BindingSet.Builder builder = builderMap.get(enclosingElement);
      Id resourceId = elementToId(element, BindView.class, id);
      //判断builder是否为空
      if (builder != null) {
        //不空,在查找是否存在了,存在就是重复绑定了 打印异常 返回
        String existingBindingName = builder.findExistingBindingName(resourceId);
        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 {
        //为空 则生成一个Builder出来
        builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
      }
      //获取名字、类型
      String name = simpleName.toString();
      TypeName type = TypeName.get(elementType);
      //是否有 Nullable 标签
      boolean required = isFieldRequired(element);
      //在builder中添加一个Field ,主要是FieldViewBinding,如果是@BindDrawable则会用FieldDrawableBinding类
      builder.addField(resourceId, new FieldViewBinding(name, type, required));
    
      //最后添加到set中,erasedTargetNames这个对象主要的作用是在后面
      erasedTargetNames.add(enclosingElement);
    }
    
    

    这里也不是很复杂,也就是根据注解获取到的内容,判断变量是不是View的子类、不是static、不是private等等,然后获取名字、Id,然后用 FieldViewBinding 类包装一下
    到这里其实已经根据所有有注解的内容已经按照 “类” 分好了,分好还不行,肯定会有什么BaseActivity、BaseDialog、BaseView所以要整合,让生成的类有联系

    第二部分
    Map<TypeElement, ClasspathBindingSet> classpathBindings =
            findAllSupertypeBindings(builderMap, erasedTargetNames);
    //组合所有类的关系  组成 树
    //这里注释也写了,用队列的方式,将超类与子类绑定,从根开始
    // Associate superclass binders with their subclass binders. This is a queue-based tree walk
    // which starts at the roots (superclasses) and walks to the leafs (subclasses).
    //这个获取的所有的“类”  放入了队列
    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();
      //拿队列出来的第一个 在erasedTargetNames中查询,父类是否在这个这个集合里
      //为什么要这样? 因为可能 你在父类中 绑定了一个String
      //在子类中使用了这个String,所以必须先初始化父类的String
      //翻看生成好的代码来看,初始化都是在构造函数中进行绑定的
      //所以考虑继承情况,必须把所有的类进行一个关联。
      TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
      if (parentType == null) {
        //如果没有父类,则直接 放入
        bindingMap.put(type, builder.build());
      } else {
        //如果父类有绑定
        //再从bindingMap(即将返回的对象)中取看看 是否已经放进去了
        BindingInformationProvider parentBinding = bindingMap.get(parentType);
        if (parentBinding == null) {
          //如果没绑定进去 再从classpathBindings中取 一个父类
          parentBinding = classpathBindings.get(parentType);
        }
        if (parentBinding != null) {
          //如果这个父类不是空的 则和当前循环里的builder  子、父类绑定
          builder.setParent(parentBinding);
          //放入即将返回的map里
          bindingMap.put(type, builder.build());
        } else {
          //翻译是:有个超类绑定,但还没有构建它,放到后面继续排队
          // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
          //比如:子类继承父类都有绑定,这时候子类需要关联父类,父类还没初始化,
          //把子类放到队尾,等父类初始化完成在进行关联
          entries.addLast(entry);
        }
      }
    }
    //返回,这时候这个map的key 就全是“类信息”
    //value就是获取好的 当前类里面需要绑定的内容
    //并且 “类” 也已经做好了继承关系
    return bindingMap;
    

    看着代码挺多,主要就2个部分
    以View为例,收集View信息,规制到同一个类中,把有关联的类,关联起来

    2、binding.brewJava(sdk, debuggable)

    到这里就简单多了

     Map<TypeElement, BindingSet> bindingMap =
    

    照着这个bindingMap对象 遍历里面的“类”,生成文件即可
    这里面用了另一个库 javapoet 专门用来生成类文件的

    JavaFile brewJava(int sdk, boolean debuggable) {
      TypeSpec bindingConfiguration = createType(sdk, debuggable);
      return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
          .addFileComment("Generated code from Butter Knife. Do not modify!")
          .build();
    }
    //这个方法
    createType(sdk, debuggable)
    
    private TypeSpec createType(int sdk, boolean debuggable) {
      TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
          .addModifiers(PUBLIC)
          .addOriginatingElement(enclosingElement);
      if (isFinal) {
        result.addModifiers(FINAL);
      }
    
      if (parentBinding != null) {
        result.superclass(parentBinding.getBindingClassName());
      } else {
        result.addSuperinterface(UNBINDER);
      }
    
      if (hasTargetField()) {
        result.addField(targetTypeName, "target", PRIVATE);
      }
    
      if (isView) {
        result.addMethod(createBindingConstructorForView());
      } else if (isActivity) {
        result.addMethod(createBindingConstructorForActivity());
      } else if (isDialog) {
        result.addMethod(createBindingConstructorForDialog());
      }
      if (!constructorNeedsView()) {
        // Add a delegating constructor with a target type + view signature for reflective use.
        result.addMethod(createBindingViewDelegateConstructor());
      }
      result.addMethod(createBindingConstructor(sdk, debuggable));
    
      if (hasViewBindings() || parentBinding == null) {
        result.addMethod(createBindingUnbindMethod(result));
      }
    
      return result.build();
    }
    
    

    这里就不加注释了,这 PUBLIC、FINAL
    下面的判断isView、isActivity、isDialog,点一个进去

    private MethodSpec createBindingConstructorForActivity() {
      MethodSpec.Builder builder = MethodSpec.constructorBuilder()
          .addAnnotation(UI_THREAD)
          .addModifiers(PUBLIC)
          .addParameter(targetTypeName, "target");
      if (constructorNeedsView()) {
        builder.addStatement("this(target, target.getWindow().getDecorView())");
      } else {
        builder.addStatement("this(target, target)");
      }
      return builder.build();
    }
    

    这些 builder.addStatement 的内容,打开变异后的文件,都能看到
    这里仅仅是生成文件的一些工具方法
    最后就是 JavaFile.builder 建造者返回一个 build 对象
    最后 javaFile.writeTo(filer); 生成文件

    BindingSet

    一个比较关键的类,是一个类里面,需要绑定的所有内容,比如String、View、Drawable等
    代码太多,我就贴部分内容
    一开始会有很多的 static final ClassName 声明的内容,ClassNamejavapoet 里面的,都是生成文件需要的包名
    成员变量:

    //隐藏了看变量名就能看懂的 变量
    private final ImmutableList<ViewBinding> viewBindings;
    private final ImmutableList<FieldCollectionViewBinding> collectionBindings;
    private final ImmutableList<ResourceBinding> resourceBindings;
    private final @Nullable BindingInformationProvider parentBinding;
    

    ImmutableList:这是JDK提供的一些集合实现,当你不希望修改一个集合类,或者想做一个常量集合类可以使用它,当list用就行。
    这里分成了3种情况进行绑定,BindViewBindViewsbindResource

    BindView

    绑定一个View ,ViewBinding 类是一个view的信息,生成 R文件 的常量,xml 里面 id 的名字,如果有 Listener 则会有事件的信息方法等等

    BindViews

    绑定一群View, FieldCollectionViewBinding 类是一群View的信息,里面有个List<Id>,还有个type也就是一群View的类型

    bindResource

    绑定资源,比如Anim、String、Drawable
    肯定因为绑定,其实也就是赋值的代码不好重复,和 java库工程里没有View内容所以分开的
    ResourceBinding 是个接口
    FieldAnimationBindingFieldDrawableBindingFieldResourceBindingFieldTypefaceBinding 这4个类继承了 ResourceBinding 接口,就是让这几个类确定 最后生成类文件里的变量 怎样 编写赋值代码

    interface ResourceBinding {
      Id id();
    
      boolean requiresResources(int sdk);
    
      CodeBlock render(int sdk);
    }
    final class FieldDrawableBinding implements ResourceBinding {
    //移除部分代码
    @Override public CodeBlock render(int sdk) {
        if (tintAttributeId.value != NO_RES_ID) {
          return CodeBlock.of("target.$L = $T.getTintedDrawable(context, $L, $L)", name, UTILS, id.code,
              tintAttributeId.code);
        }
        if (sdk >= 21) {
          return CodeBlock.of("target.$L = context.getDrawable($L)", name, id.code);
        }
        return CodeBlock.of("target.$L = $T.getDrawable(context, $L)", name, CONTEXT_COMPAT, id.code);
      }
    }
    

    render(int sdk) 根据sdk版本,具体生成出当前版本 Drawable赋值方式
    到这里基本知道了ButterKnife是如何帮我们完成findViewById的

    学艺不精,如果内容有错误请及时联系我,我及时改正

    相关文章

      网友评论

          本文标题:学习笔记ButterKnife

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