ButterKnife(8.4.0版本)原理分析

来源:互联网 发布:自动打印软件 编辑:程序博客网 时间:2024/05/18 21:07

        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)   //注册处理器,这样在编译时才会调用ButterKnifeProcessorpublic 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开发居家必备的良品。

0 1