ButterKnife源码阅读

ButterKnife 框架结构
  • butterknife:提供绑定的入口,传入view以及target目标
  • butterknife-annotations:定义了一系列如:view,onclick等参数、方法、成员变量等类型的运行时注解信息
  • butterknife-compiler:封装了注解处理器(核心)
  • butterknife-reflect
  • butterknife-runtime

1. 绑定流程

1.1 butterknife模块实现了绑定UI对象的入口

以绑定activity为例开始分析

  • ButterKnife.bind(Activity activity):通过bind方法将Activity以及Activity顶级View作为参数传入

  • 其中butterknife会根据注解对象(即Acitivity)生成对应的ViewBinding类,这个生成的过程在注解处理器中说明,我们姑且称之为“绑定类

  • 通过反射返回绑定类的实例对象,即Unbinder对象(绑定类是继承自Unbinder的)

    public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();//1.1 获取Activity最顶级的view
        return bind(target, sourceView);
    }
      
    public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        Class<?> targetClass = target.getClass();//1.2 获取绑定对象的类名
        
        //1.3 通过Activity的class名,获取类名为clsName + "_ViewBinding"的类构造器
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        if (constructor == null) {
          return Unbinder.EMPTY;
        }
        
        ...
        //1.9 通过反射创建绑定类的对象
        return constructor.newInstance(target, source);//try-catch
    }  
    
  • findBindingConstructorForClass:获取butterknife生成的Activity绑定类的构造器

    • BINDINGS为构造器对应绑定类名的缓存集合
    • BINDINGS可以看出绑定类是继承自Unbinder类
    • 查询对象绑定类时,如果查询不到,会不断递归向上查询父类的绑定类直到android原生类为止
  //构造器缓存
  static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
  
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //1.4 有缓存则取缓存
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      return bindingCtor;
    }
    
    //1.5 因为这个绑定的或者是不断递归向上的:查询不到对象绑定类时,会查询对象父类的绑定类
    //    所以当查询对象为android原生类时,则会停止
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")|| clsName.startsWith("androidx.")) 
       return null;
    
    try {//1.6 获取对象对应的绑定类的构造器对象
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    } catch (ClassNotFoundException e) {
     //1.7 当获取不到时(不存在),则递归查询对象父类的绑定类
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
      ...
    }
    BINDINGS.put(cls, bindingCtor);// 1.8 存入缓存
    return bindingCtor;
  }
1.2 绑定类的结构
  • 绑定类位于app\build\generated\source\apt\debug\com.haha.updateass.MainActivity_ViewBinding

    • MainActivity_ViewBinding的构造函数刚好与上面反射创建绑定类对应
    • 通过findRequiredView查询到Button资源,再通过castView转换为Activity对应的Button对象,添加点击监听
    • 调用Unbinder对象的unbind方法时,将资源关联都置为null
    // Generated code from Butter Knife. Do not modify!
    package com.haha.updateass;
    
    import android.support.annotation.CallSuper;
    import android.support.annotation.UiThread;
    import android.view.View;
    import android.widget.Button;
    import butterknife.Unbinder;
    import butterknife.internal.DebouncingOnClickListener;
    import butterknife.internal.Utils;
    import java.lang.IllegalStateException;
    import java.lang.Override;
    
    public class MainActivity_ViewBinding implements Unbinder {
      private MainActivity target;
      private View view2131165219;
    
      @UiThread
      public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    
      @UiThread
      public MainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
    
        View view;
        view = Utils.findRequiredView(source, R.id.bt_load, "field 'btLoad' and method 'onViewClicked'");
        target.btLoad = Utils.castView(view, R.id.bt_load, "field 'btLoad'", Button.class);
        view2131165219 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.onViewClicked(p0);
          }
        });
      }
    
      @Override
      @CallSuper
      public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
        target.btLoad = null;
        view2131165219.setOnClickListener(null);
        view2131165219 = null;
      }
    }
    
    
  • findRequiredView:里面通过findViewById查找对应的资源

    • 方法位于D:\github\butterknife-master\butterknife-runtime\src\main\java\butterknife\internal\Utils.java
      public static View findRequiredView(View source, @IdRes int id, String who) {
        View view = source.findViewById(id);
        ...
      }
    
  • castView:将view强转为对应的class类型

    • 方法位于D:\github\butterknife-master\butterknife-runtime\src\main\java\butterknife\internal\Utils.java
      public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
          return cls.cast(view);//try-catch
        ...
      }
    

看到这里会有疑问:绑定类中查找资源,强转资源类型都能理解,但是这些资源信息到底是如何生成的?接下来看注解处理器Butterknife-Processor

2. butterknife注解处理器

通过对Android注解处理器的学习,我们可以看到butterknife自定义的一个注解处理器ButterKnifeProcessor,他通过继承AbstractProcessor实现对应的方法。对于Android注解器的使用可以参考 link

2.1 注册注解器
  • @AutoService(Processor.class) 是google提供给我们的自动向javac注册注解器的方法,避免手动去配置注解器

    @AutoService(Processor.class)
    @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
    @SuppressWarnings("NullAway") // TODO fix all these...
    public final class ButterKnifeProcessor extends AbstractProcessor {
        ...
    }
    
  • ButterKnifeProcessor继承AbstractProcessor,重写了几个重要方法

    • init(ProcessingEnvironment env) 里面判断了sdk版本,拿到了Types ,Filer ,Trees 等实例,其中Filer主要用作创建新文件,比如类文件

    • getSupportedOptions() 获取注解器支持的配置项

    • 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<>();
          //添加ButterKnife支持的注解类型
          annotations.add(BindAnim.class);
          annotations.add(BindArray.class);
          annotations.add(BindBitmap.class);
          ...
          
          return annotations;
      }
        
      
    • process(Set<? extends TypeElement> elements, RoundEnvironment env) 核心方法,查询和处理注解,生成java文件

2.2 process
  • process方法查询所有带注解的节点并封装

    @Override public boolean procesButterKnifes(Set<? extends TypeElement> elements, RoundEnvironment env) {
        //1. 查询获取所有带ButterKnife绑定注解类型的元素集合
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
        //2. 通过查询到的bind元素,根据特定规则生成java文件
        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue();
    
          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;
      }
    
  • findAndParseTargets 查询获取所有带ButterKnife bind注解类型的元素集合,以bindView为例

      private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
        
        ... //省略部分节点如BindInt.class
    
        // 获取BindView 标记的Element
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
          try {
            //解析BindView注解信息
            parseBindView(element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindView.class, e);
          }
        }
        
        ...//中间有解析添加那些带监听的注解
    
        Map<TypeElement, ClasspathBindingSet> classpathBindings =
            findAllSupertypeBindings(builderMap, erasedTargetNames);
    
        // 将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, classpathBindings.keySet());
          if (parentType == null) {
          //将TypeElement, BindingSet.Builder对应存入集合
            bindingMap.put(type, builder.build());
          } else {
            BindingInformationProvider parentBinding = bindingMap.get(parentType);
            if (parentBinding == null) {
              parentBinding = classpathBindings.get(parentType);
            }
            if (parentBinding != null) {
                //当有父类节点是,通过setParent记录到BindingSet
              builder.setParent(parentBinding);
              bindingMap.put(type, builder.build());
            } else {
              // 具有父类绑定,但我们尚未构建它。 重新加入队列末尾
              entries.addLast(entry);
            }
          }
        }
    
        return bindingMap;
      }
    
  • parseBindView 解析view注解的信息封装成BindingSet,与之TypeElement对应存储到集合中

      private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
          Set<TypeElement> erasedTargetNames) {
          
          //getEnclosingElement 返回封装此元素(非严格意义上)的最里层元素。
          //可以理解上一级,父类的view元素
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
        //验证当前fields的权限类型以及是否位andriod原生api或者成员
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
            || isBindingInWrongPackage(BindView.class, element);
    
        // 验证目标类型是否从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();
        if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
          ...//判断当前元素是否是View的子类,或者是接口,不是的话抛出异常 
        }
        if (hasError) return;
    
        //解析View的属性值,这里是view的id
        int id = element.getAnnotation(BindView.class).value();
        //查询是否存在父类以及绑定的情况
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        Id resourceId = elementToId(element, BindView.class, id);
        if (builder != null) {
          ... //存在View的父类以及绑定,则校验此id是否被绑定,如果已绑定则抛出异常
        } else {
            //创建BindingSet.Builder,将enclosingElement存入builderMap
          builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }
    
        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
        //将元素对应的信息存入到builder中
        builder.addField(resourceId, new FieldViewBinding(name, type, required));
    
        // 将view的父类元素存入erasedTargetNames集合
        erasedTargetNames.add(enclosingElement);
      }
    
    
  • BindingSet :封装了bind类的注解元素信息,用来生成特定的类

    private BindingSet(
          TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement,
          boolean isFinal, boolean isView, boolean isActivity, boolean isDialog,
          ImmutableList<ViewBinding> viewBindings,
          ImmutableList<FieldCollectionViewBinding> collectionBindings,
          ImmutableList<ResourceBinding> resourceBindings,
          @Nullable BindingInformationProvider parentBinding) {
        this.isFinal = isFinal;
        this.targetTypeName = targetTypeName;
        this.bindingClassName = bindingClassName;
        this.enclosingElement = enclosingElement;
        this.isView = isView;
        this.isActivity = isActivity;
        this.isDialog = isDialog;
        this.viewBindings = viewBindings;
        this.collectionBindings = collectionBindings;
        this.resourceBindings = resourceBindings;
        this.parentBinding = parentBinding;
      }
    

3. 生成绑定类

里面用到了JavaPoet技术具体参考https://blog.csdn.net/l540675759/article/details/82931785

在上述process中拿到BindingSet后通过其brewJava(sdk, debuggable)方法来生成JavaFile对象,
而生成JavaFile的方法则是通过JavaPoet开源库完成。

  • brewJava
  JavaFile brewJava(int sdk, boolean debuggable) {
    //TypeSpec 包含了类,接口,方法声明
    //createType则对应生成TypeSpec信息
    TypeSpec bindingConfiguration = createType(sdk, debuggable);
    return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

4. 结尾

ButterKnife总体的框架原理很简单,首先生成对应的绑定类,然后通过反射完成绑定类的实例化和注册。但是根据注解对应去生成绑定类,里面封装了太多的细节和内容。其中用到了annotationProcessor 和JavaPoet 技术,当然里面也很有很多值得学习的地方,每一个好的开源框架里面不可少的缓存机制和模式使用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值