ButterKnife(8.4.0版本)原理分析

        ButterKnife是鼎鼎大名的JakeWharton写的注解框架, 将你从findViewById这样无聊的体力活解脱出来。  github地址: https://github.com/JakeWharton/butterknife  , 已超过1万颗星了,   很屌。

       JakeWharton是square公司的大咖,  是Piccaso(图片开源框架)、RxJava/RxAndroid(响应式编程)、OkHttp(Http通讯开源框架)的主要开发者。 不夸张的说, 对于一个做互联网app的码农来说, 如果不知道他就是少见识了。

       ButterKnife的集成方式和使用方法已在github上描述,    就不多说了。  8.4.0版本做了一些优化:

1、 删除了ButterKnife类的unbind方法。  改为保存ButterKnife.bind函数返回的Unbinder引用, 在onDestroy函数里调用Unbinder的unbind方法。 

2、如下图所示,在编译过程会生成*_ViewBinding.java,  如SimpleActivity_ViewBinding.java。

3、ButterKnife对性能没影响,  一些人说用了注解和反射影响性能, 这个锅ButterKnife不背。 增加Java方法数量和apk体积到是真的, 毕竟生成了_ViewBinding.java文件。


基础知识:APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定), APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。


生成*_ViewBinding.java文件的原理:在编译时期,javac会调用java注解处理器(APT)进行处理,通过自定义注解处理器来实现想要的功能, 例如ButterKnife在编译期间生成Java文件。

 

下面看ButterKnife的核心代码ButterKnifeProcessor.java, 在Android编译期间

@AutoService(Processor.class)   //注册处理器,这样在编译时才会调用ButterKnifeProcessor
public final class ButterKnifeProcessor extends AbstractProcessor {
  ...
  //支持的监听器
 private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(//
      OnCheckedChanged.class, //
      OnClick.class, //
      OnEditorAction.class, //
      OnFocusChange.class, //
      OnItemClick.class, //
      OnItemLongClick.class, //
      OnItemSelected.class, //
      OnLongClick.class, //
      OnPageChange.class, //
      OnTextChanged.class, //
      OnTouch.class //
  );
  //支持注解的资源类型
  private static final List<String> SUPPORTED_TYPES = Arrays.asList(
      "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string"
  );
  ...   

  //指定使用的Java版本
  @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    ...
  }

  //返回stirng类型的set集合,集合里包含了需要处理的注解</span></code>类型
  @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(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(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }

  //核心函数, 在这个函数里生成*_ViewBinding.java
  @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //查找所有的注解信息,并形成BindingClass保存到 map中
   Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); 

    //遍历bindingMap生成类名_ViewBinding的java文件
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return true;
  }

  //解析所有的注解
  private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = 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, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindArray.class, e);
      }
    }

    // Process each @BindBitmap element.
    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBitmap(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBitmap.class, e);
      }
    }

    // Process each @BindBool element.
    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBool(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBool.class, e);
      }
    }

    // Process each @BindColor element.
    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceColor(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindColor.class, e);
      }
    }

    // Process each @BindDimen element.
    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDimen(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDimen.class, e);
      }
    }

    // Process each @BindDrawable element.
    for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDrawable(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDrawable.class, e);
      }
    }

    // Process each @BindFloat element.
    for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceFloat(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindFloat.class, e);
      }
    }

    // Process each @BindInt element.
    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceInt(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindInt.class, e);
      }
    }

    // Process each @BindString element.
    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceString(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindString.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, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

    // Process each @BindViews element.
    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindViews(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindViews.class, e);
      }
    }

    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, 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();

      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
        BindingSet parentBinding = bindingMap.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;
  }
  ...
}

    下面看Java文件的生成方法:

  JavaFile brewJava() {
  //参数1:包名
   //参数2:TypeSpec,这个可以生成class ,interface 等java文件
    //注意addFileComment的参数,已经说明是生成的代码了
   return JavaFile.builder(bindingClassName.packageName(), createBindingClass())
        .addFileComment("<span style="color:#FF0000;">Generated code from Butter Knife. Do not modify!</span>")
        .build();  }

bind:

如何才能生成的Java文件呢?

答案是: ButterKnife.bind(this);方法。


unbind:

在新版的8.4.0中去除了 unbind方法。

<span style="font-size:18px;">ButterKnife.unbind  //已经删除了</span>

并采用了接口的形式,让生成的类来实现释放引用。 例如:

<span style="font-size:18px;"> public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder {
   @UiThread
  public SimpleAdapter$ViewHolder_ViewBinding(SimpleAdapter.ViewHolder target, View ) {
  //...
  }
 //...
  @Override
  public void unbind() {
  //...
  }
 }</span>

那如何unbind呢?ButterKnife.bind(this)返回值是一个Unbinder引用。
  所以可以这样:

<span style="font-size:18px;"> Unbinder mUnbinder;     
 @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
     mUnbinder=ButterKnife.bind(this);  //保存引用
    }
      @Override
  protected void onDestroy() {
     super.onDestroy();
    mUnbinder.unbind();  //释放所有绑定的view
  }</span>

     综上, ButterKnife是个好东西,节省开发时间而且不影响性能。 是Android开发居家必备的良品。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值