ButterKnife的使用介绍及原理探究(二)

本文深入探讨ButterKnife的工作原理,包括自定义注解、注解处理器及源码分析等内容,帮助读者理解ButterKnife如何在编译时生成辅助类以提高应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、预备知识

通过上篇文章的介绍,相信已经对ButterKnife的使用熟悉了,网上也有很多讲解其使用的文章。 本篇主要探究一下ButterKnife的实现原理。但是在开始分析之前,可能还需要了解一下Java注解器Annotation Processor.

推荐一篇文章: Java注解处理器

读完这篇文章,相信你肯定会对Java注解处理器有了比较深的了解。

二、原理

       如果让我们实现一些注解的框架时,很容易联想到利用反射来实现。但是我们知道如果通过反射,是在运行时(Runtime)来处理View的绑定等一些列事件的,这样比较耗费资源,会影响应用的性能。

所以,ButterKnife并不是直接使用了注解+反射的方式实现,而是使用了Java 注解器Annotation Processor ,自定义注解,并注册相应的注解处理器,在编译时(而不是运行时)生成了相应的辅助类。在使用时直接通过辅助类来完成相关操作,这样就不会产生过多的消耗而出现影响性能的问题。

在此做个延伸,我们可能还熟悉另外一个注解框架xUtils, github地址:点击打开链接

这个框架注解机制,其实就是使用了注解+反射实现的。研究其源码可以看到:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {

    int value();

    /* parent view id */
    int parentId() default 0;
}

其自定义注解ViewInject 便是RetentionPolicy.RUNTIME.

这里我们不做太多解释,后面会针对xUtils开一篇博文探究其实现原理。

三、源码分析

接下来,我们就进入正题了,对ButterKnife的实现原理一步一步地分析了。

3.1 自定义注解

ButterKnife自定义了很多我们常用的注解,比如@BindView,@OnClick等. 


通过@Target(FIELD) 注解可以看出,BindView注解是使用在成员变量上的。


还有其他的注解,可以在源码butterknife-annotations.butterknife包下查看。 

有了这些注解,那还得需要有注解处理器呀!ButterKnife的注解处理器ButterKnifeProcessor. 那接下来就来看看这个处理器。

3.2 ButterKnifeProcessor

在分析ButterKnifeProcessor之前,可以看到它 继承自 AbstractProcessor. 我们就先来介绍一下AbstractProcessor.

自定义的Processor都必须继承自AbstractProcessor,并重写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;
    }
}

每个方法的具体功能,注释都写的很清楚,不必再过多解释。

要想JVM调用你的处理器,首先需要注册,怎样让JVM知道呢,google 为我们提供了一个库,简单的一个注解就可以。 

compile 'com.google.auto.service:auto-service:1.0-rc2'

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
  //...省略非关键代码
}

那接下来看ButterKnifeProcessor源码:

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
  private Elements elementUtils;
  private Types typeUtils;
  private Filer filer;
  private Trees trees;
  ...
@Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    String sdk = env.getOptions().get(OPTION_SDK_INT);
    if (sdk != null) {
      try {
        this.sdk = Integer.parseInt(sdk);
      } catch (NumberFormatException e) {
        env.getMessager()
            .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
                + sdk
                + "'. Falling back to API 1 support.");
      }
    }

    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }
...
@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;
  }
 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    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 false;
  }
......
}

同样,ButterKnifeProcessor也是实现了那几个方法。先来看看init方法

init()

init(ProcessingEnvironment env)方法,ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。在解析器工作的时候,会扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。也就是说Element代表程序的元素,例如包、类或者方法。而Types是用来TypeMirror的工具类,Filer用来创建生成辅助文件,在这之前先看一下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).

换句话说:Element代表程序中的元素,比如说 包,类,方法。每一个元素代表一个静态的,语言级别的结构. 例如:

public class ClassA { // TypeElement
    private int var1; // VariableElement
    public ClassA() {} // ExecuteableElement

    public void setA( // ExecuteableElement
            int newA // TypeElement
    ) {
    }
}

这些是理解源码的基础。

getSupportedAnnotationTypes()

getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注册给哪些注解的。它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。源码中可以看出ButterknifeProcessor可以处理BindView.class等一系列注解。

getSupportedSourceVersion() 

getSupportedSourceVersion()用来指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),Butterknife也是如此。

process() 

process()首先通过findAndParseTargets(env)方法扫描所有具有注解的类,并根据这些类的信息生成BindingSet,最后形成以TypeElement为键,BindingSet为值的键值对。接着循环遍历这个键值对,根据TypeElement和BindingSet里面的信息生成对应的java类。例如MainActivity生成的类即MainActivity$$ViewBinder类, 这就是前边所说的辅助类,后面会详细介绍。也就是说,process()方法主要干了两件事,一是收集信息,二是生成代码。

查看process()实现可以看出,通过findAndParseTargets 方法收集信息最终保存在Map 集合中,现在来看看findAndParseTargets源码。

 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 @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;
  }

省略的部分是将各注解进行分拆遍历,并且进行解析,最后将解析的结果放入bindingMap,原理和解析Bind注解是一样的,这里我们只用分析一个就可以了。就分析BindView吧,通过遍历找到所有使用时BindView注解的Element,接着将得到的Element传递给parseBindView(element, builderMap, erasedTargetNames)方法,从方法名就可以看出,BindView的解析工作是在该方法中完成的,看源码吧;

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();//获取父节点
    // 1. 检查用户的合法性:是否使用private 和static修饰; 是否在android或者java开头的包名中。
    // 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. 获取BindingSet, 有缓存机制,如果没有,则会创建
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(id));
      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 = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
    // 4. 生成FieldViewBinding实体
    String name = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);
    // 5. 加入到bindingSet的集合中
    builder.addField(getId(id), new FieldViewBinding(name, type, required));

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

基本上就是以上5个步骤。 到这里就把信息收集好了。

下一篇文章将分析一下检查用户的合法性、缓存机制创建builder 和OnClick注册事件。 






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值