流行框架源码分析(4)-butterknife源码分析(一)

主目录见:Android高级进阶知识(这是总目录索引)
 前面我们已经讲完[编译期注解的使用例子]大家应该对这个流程比较熟悉了,我们今天要讲的butterknife的源码其实也是用的这个,不过里面细节还是比较多的,我今天会尽量围绕着主干讲,对于一些lint检查,R2文件的生成(这个主要是用插件完成)这些暂时会不讲。

一.目标

对于butterknife,如果使用的好的话可以帮我们省了非常多的工作,而且我们可以把这里面的知识点应用到我们的框架中去,毕竟看尽了代码才能融会贯通,今天我们目标就是:
1.复习编译期注解的流程;
2.了解butterknife中的设计思想;
3.能借鉴到自己的框架项目里面。

二.源码分析

看这个源码之前,我们首先要来看下代码的目录,让我们有个整体的认识,首先让我们祭出大图:


源码结构图
  • butterknife:提供android程序使用的api的模块
  • butterknife-annotations:java模块,里面放着butterknife支持的注解
  • butterknife-compiler:java模块,编译注解处理器就在这里
  • butterknife-gradle-plugin:这个是后面才有的,为了解决在library里面使用的问题
  • butterknife-integration-test:这个项目的测试用例
  • butterknife-lint:lint优化的代码都在这里
  • sample:demo代码
    看到了目录我们应该会有点熟悉的感觉才对,因为我们前一篇已经讲过,还有分析Eventbus3.0也有点这个蛛丝马迹。

1.生成后的源码

为了我们后面更容易地分析butterknife编译期注解的代码,我们先来看下生成的代码长得啥样,这样我们才能更好地知道我们注解处理器是怎么生成代码的:

// Generated code from Butter Knife. Do not modify!
package com.lenovohit.butterknifedemo;

import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
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 view2131427446;

  private View view2131427447;

  @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;
    target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
    view2131427446 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
    view2131427447 = view;
    ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.title = null;
    target.subtitle = null;
    target.hello = null;
    target.listOfThings = null;
    target.footer = null;
    target.headerViews = null;

    view2131427446.setOnClickListener(null);
    view2131427446.setOnLongClickListener(null);
    view2131427446 = null;
    ((AdapterView<?>) view2131427447).setOnItemClickListener(null);
    view2131427447 = null;
  }
}

这个例子我是用官网的例子生成的,因为官网上面最新的例子是支持的android studio3.0版本,所以我另外创建了一个项目放进去的。我们等会就可以对照这个来看源码了。

2.注解处理器

注解处理器流程

首先我们明确一下流程,注解处理器在编译时期会扫描到所有的带有编译期注解的注解例如@BindView @Onclick@OnLongClick等等,然后butterknife会使用javapoet来生成源码,最后我们会对生成的代码进行调用。
 那么现在我们就来看看注解处理器的代码。那么我们直接到达butterknife-compiler包下面的ButterKnifeProcessor类中,由于一些概念我们上一篇文章讲过了,所以我们这里直接看init()方法干了下啥:

@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.");
      }
    }

    debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));

    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }

我们看到这里首先获取了sdk的版本,然后初始化了elementUtils(用来处理Element的辅助类), typeUtils(用来处理TypeMirror的工具类),filer(用来生成java的源文件),同时实例化了trees对象(这个跟语法分析树有关)。然后我们看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<>();

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

 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 //
  );

我们看到这个方法里面支持的注解很多这些注解都在butterknife-annotations这个java模块里面。看了这两个方法之后,我们就直接来看注解处理器的核心方法process()。

3.process

我们知道我们前面说了process一般分成两步:1.收集信息 2.生成代码。这边也不例外:

 @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, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

这边的步骤非常清楚,所以我们这边直接来看收集信息是收集了哪些信息:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// 建立view与R的id的关系
    scanForRClasses(env);
//省略了其他注解的代码
...........
    // Process each @BindView element.
// 解析BindView注解
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

..........
    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }

// 将Map.Entry<TypeElement, BindingSet.Builder>转化为Map<TypeElement, BindingSet>
    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;
  }

这个收集信息的方法很长,我们这里就挑一个注解BindView来看,看看是怎么收集信息的。但是看这个之前我们先来看看scanForRClasses()方法是干什么的呢?

3.1 scanForRClasses

scanForRClasses主要是用来建立view与id的关系,为了是设置的@onclick等注解的视图唯一。

例如在上面所举的例子中,MainActivity_ViewBinding里会持有一个全局变量view2131427446,这个其实就是MainActivity的Button,后面的2131427446就是对应在R文件的id。

scanForRClasses里面涉及到语法分析树,我们这里简要说明下代码的流程就不细说了,因为这不是主干知识:程序首先会利用elementUtils获取到包名,然后利用RClassScanner类来查找R文件,在R文件里面用IdScanner来查找R文件中的id,在IdScanner中用VarScanner来查找Button的id,最后就得到Button对应于view2131427446。

3.2 parseBindView

因为我们这里以BindView为例,其他注解其实也是类似,所以我们分析一个注解就应该足够了。程序首先会遍历出来带BindView注解的Element,然后调用parseBindView():

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
//得到包含该注解的TypeElement(类元素),例如MainActivity
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // 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();
    }
    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;
    }

    // Assemble information on the field.
//获取到BindView里面所带的id
    int id = element.getAnnotation(BindView.class).value();
//这个地方一个类对应一个builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
//这个地方是检测这个类对应的Builder 是否已经有绑定过这个id,因为一个类中的布局中的控件id只能是唯一的
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      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);
    }
//被注解的变量的名字,类型以及是否带有@optional注解
    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);
//builder添加这个变量
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

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

这个方法比较长,首先我们来看第一个部分合法性检验,这里有两个检验方法,我们先来看isInaccessibleViaGeneratedCode()方法:

  private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
      String targetThing, Element element) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify method modifiers.
    Set<Modifier> modifiers = element.getModifiers();
//判断变量是不是private和static的
    if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
      error(element, "@%s %s must not be private or static. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Verify containing type.
//判断这个变量是不是在类中
    if (enclosingElement.getKind() != CLASS) {
      error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }
//判断类是不是private的
    // Verify containing class visibility is not private.
    if (enclosingElement.getModifiers().contains(PRIVATE)) {
      error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    return hasError;
  }

从这个检测方法可以看出,我们BindView这个注解不能用在private和static的变量上面,而且不能用在不是类且类为private的情况下。接着我们来看另外检测方法isBindingInWrongPackage():

private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
      Element element) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    String qualifiedName = enclosingElement.getQualifiedName().toString();
//类所在的包不能是以android.开头的
    if (qualifiedName.startsWith("android.")) {
      error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }
//类所在的包不能是以java.开头的
    if (qualifiedName.startsWith("java.")) {
      error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }

    return false;
  }

我们看到这里检测还是非常严谨的,我们的类所在的包名不能是android.和java.开头,也就是系统的包。接着我们看后面builder的创建和id唯一性的判断,首先我们要明确一下一个类是对应于一个builder的,首先我们来看下builder长啥样,这是BindingSet的内部类:

  static final class Builder {
//添加有注解的对应类的类名
    private final TypeName targetTypeName;
//注解处理器生成源码文件类的类名
    private final ClassName bindingClassName;
//这些是标记,标识是不是final的,view,activity,dialog
    private final boolean isFinal;
    private final boolean isView;
    private final boolean isActivity;
    private final boolean isDialog;

    private BindingSet parentBinding;
//一个id对应于一个ViewBinding.Builder(这个Builder里面包含对应的变量名称,类型,是否required,
//且如果有事件还可能对应的监听事件和监听方法相关信息)
    private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
    private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings =
        ImmutableList.builder();
    private final ImmutableList.Builder<ResourceBinding> resourceBindings = ImmutableList.builder();
}

从我们上面注释可以看出这个Builder其实就是对应于我们要生成代码所需要的所有信息,我们只要把这些信息构造进去即可。我们知道我们builder不存在的时候我们会去创建一个,程序会调用getOrCreateBindingBuilder()方法,所以我们这里跟进这个方法看下:

  private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
  }

我们看到程序首先会从builderMap中查找这个类有没有对应的Builder,如果没有则调用newBuilder来创建,所以我们再跟进newBuilder()方法:

  static Builder newBuilder(TypeElement enclosingElement) {
    TypeMirror typeMirror = enclosingElement.asType();

    boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
    boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
    boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
      targetType = ((ParameterizedTypeName) targetType).rawType;
    }

    String packageName = getPackage(enclosingElement).getQualifiedName().toString();
    String className = enclosingElement.getQualifiedName().toString().substring(
        packageName.length() + 1).replace('.', '$');
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
  }

这个方法其实很简单,首先就是判断isView ,isActivity ,isDialog 是不是true,然后获取带有此注解的类的类名,接着获取要生成的源代码的类名例如:MainActivity_ViewBinding或者SimpleAdapter$ViewHolder_ViewBinding。到这里解析BindView注解的流程已经完毕了。但是我们知道,我们ButterKnife不一定只会用在视图上面,我们也可以用在方法上面,如@onClick@onLongClick等等。所以我们接下来要来分析这方面的代码。

4.事件的注解

事件的注解相对于控件的注解会比较麻烦一点,首先我们来看下我们注解在方法上面的样子:

事件注解使用

然后我们看生成的代码的样子:

生成代码的样子

从两图对比我们可以很清楚地看到我们程序自动补足了setOnclickListener这些部分,sayHello变成doClick方法里面的方法调用。那么这些setOnclickListener和doClick还有DebouncingOnClickListener是哪里来的呢?我们先去看看@OnClick注解一下:

OnClick注解

其他的注解类似,我们看到OnClick注解里面又有注解@ListernerClass而且method里面还有注解@ListernerMethod,所以我们能想到,我们程序肯定是获取了OnClick上面的注解然后进行用javaPoet拼接的嘛。带着这个想法我们来看下源码:

  // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }

我们看见这个代码是遍历所有的LISTENERS中所有的注解,然后调用findAndParseListener方法,那么我们直接来看这个方法:

  private void findAndParseListener(RoundEnvironment env,
      Class<? extends Annotation> annotationClass,
      Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
//得到带有此注解的元素然后遍历
    for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
//检查合法性问题
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        StringWriter stackTrace = new StringWriter();
        e.printStackTrace(new PrintWriter(stackTrace));

        error(element, "Unable to generate view binder for @%s.\n\n%s",
            annotationClass.getSimpleName(), stackTrace.toString());
      }
    }
  }

上面的代码很简单,其实还是调用了parseListenerAnnotation方法,我们同样跟进去:

private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
      Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames)
      throws Exception {
    // This should be guarded by the annotation's @Target but it's worth a check for safe casting.
//判断这个注解是不是加在方法上面的
    if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
      throw new IllegalStateException(
          String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
    }
//获取这个方法Element
    ExecutableElement executableElement = (ExecutableElement) element;
//获取这个方法所在的类
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Assemble information on the method.
//获取这个注解中value的值
    Annotation annotation = element.getAnnotation(annotationClass);
    Method annotationValue = annotationClass.getDeclaredMethod("value");
//因为value中的值是对应于控件的id,所以必须要为int
    if (annotationValue.getReturnType() != int[].class) {
      throw new IllegalStateException(
          String.format("@%s annotation value() type not int[].", annotationClass));
    }
//获取到这个注解对应的所有id
    int[] ids = (int[]) annotationValue.invoke(annotation);
    String name = executableElement.getSimpleName().toString();
    boolean required = isListenerRequired(executableElement);

    // Verify that the method and its containing class are accessible via generated code.
//合法性检测
    boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element);
    hasError |= isBindingInWrongPackage(annotationClass, element);

    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)",
          annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }
//获取注解(例如OnClick)上面的注解ListenerClass
    ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
    if (listener == null) {
      throw new IllegalStateException(
          String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(),
              annotationClass.getSimpleName()));
    }

    for (int id : ids) {
      if (id == NO_ID.value) {
//如果id不存在
//且id如果为1的话且是optional的那就是错误的
        if (ids.length == 1) {
          if (!required) {
            error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)",
                enclosingElement.getQualifiedName(), element.getSimpleName());
            hasError = true;
          }
        } else {
          error(element, "@%s annotation contains invalid ID %d. (%s.%s)",
              annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
      }
    }

    ListenerMethod method;
//获取注解ListenerClass中对应的ListenerMethod
    ListenerMethod[] methods = listener.method();
    if (methods.length > 1) {
      throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.",
          annotationClass.getSimpleName()));
    } else if (methods.length == 1) {
//方法如果只有一个的话,那么callbacks必须为空不然就抛出错误
      if (listener.callbacks() != ListenerClass.NONE.class) {
        throw new IllegalStateException(
            String.format("Both method() and callback() defined on @%s.",
                annotationClass.getSimpleName()));
      }
      method = methods[0];
    } else {
//反射调用callback方法
      Method annotationCallback = annotationClass.getDeclaredMethod("callback");
      Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
      Field callbackField = callback.getDeclaringClass().getField(callback.name());
      method = callbackField.getAnnotation(ListenerMethod.class);
////如果没有ListenerMethod.class注解 抛出异常
      if (method == null) {
        throw new IllegalStateException(
            String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
                annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
                callback.name()));
      }
    }

    // Verify that the method has equal to or less than the number of parameters as the listener.
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
//判断注解的方法的参数不能多于ButterKnife中的ListenerMethod中的方法的方法参数(为什么呢?
//因为最后生成的代码里面要去调用我们注解的方法,如果注解的方法参数多的话那么就导致有几个参数只能是外部的
//,所以显然是不合理的)
    if (methodParameters.size() > method.parameters().length) {
      error(element, "@%s methods can have at most %s parameter(s). (%s.%s)",
          annotationClass.getSimpleName(), method.parameters().length,
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

    // Verify method return type matches the listener.
//判断注解的方法的返回值和ListenerMethod中的方法的返回值必须一样
    TypeMirror returnType = executableElement.getReturnType();
    if (returnType instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) returnType;
      returnType = typeVariable.getUpperBound();
    }
    if (!returnType.toString().equals(method.returnType())) {
      error(element, "@%s methods must have a '%s' return type. (%s.%s)",
          annotationClass.getSimpleName(), method.returnType(),
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

//判断ListenerMethod中方法的方法参数是不是包含所有的注解方法的参数
    Parameter[] parameters = Parameter.NONE;
    if (!methodParameters.isEmpty()) {
      parameters = new Parameter[methodParameters.size()];
      BitSet methodParameterUsed = new BitSet(methodParameters.size());
      String[] parameterTypes = method.parameters();
      for (int i = 0; i < methodParameters.size(); i++) {
        VariableElement methodParameter = methodParameters.get(i);
        TypeMirror methodParameterType = methodParameter.asType();
        if (methodParameterType instanceof TypeVariable) {
          TypeVariable typeVariable = (TypeVariable) methodParameterType;
          methodParameterType = typeVariable.getUpperBound();
        }

        for (int j = 0; j < parameterTypes.length; j++) {
          if (methodParameterUsed.get(j)) {
            continue;
          }
          if ((isSubtypeOfType(methodParameterType, parameterTypes[j])
                  && isSubtypeOfType(methodParameterType, VIEW_TYPE))
              || isTypeEqual(methodParameterType, parameterTypes[j])
              || isInterface(methodParameterType)) {
            parameters[i] = new Parameter(j, TypeName.get(methodParameterType));
            methodParameterUsed.set(j);
            break;
          }
        }
        if (parameters[i] == null) {
          StringBuilder builder = new StringBuilder();
          builder.append("Unable to match @")
              .append(annotationClass.getSimpleName())
              .append(" method arguments. (")
              .append(enclosingElement.getQualifiedName())
              .append('.')
              .append(element.getSimpleName())
              .append(')');
          for (int j = 0; j < parameters.length; j++) {
            Parameter parameter = parameters[j];
            builder.append("\n\n  Parameter #")
                .append(j + 1)
                .append(": ")
                .append(methodParameters.get(j).asType().toString())
                .append("\n    ");
            if (parameter == null) {
              builder.append("did not match any listener parameters");
            } else {
              builder.append("matched listener parameter #")
                  .append(parameter.getListenerPosition() + 1)
                  .append(": ")
                  .append(parameter.getType());
            }
          }
          builder.append("\n\nMethods may have up to ")
              .append(method.parameters().length)
              .append(" parameter(s):\n");
          for (String parameterType : method.parameters()) {
            builder.append("\n  ").append(parameterType);
          }
          builder.append(
              "\n\nThese may be listed in any order but will be searched for from top to bottom.");
          error(executableElement, builder.toString());
          return;
        }
      }
    }

//构造Builder对象
 MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
    BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    for (int id : ids) {
      QualifiedId qualifiedId = elementToQualifiedId(element, id);
      if (!builder.addMethod(getId(qualifiedId), listener, method, binding)) {
        error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)",
            id, enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    }

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

我们看到这一串代码比较长,但是其实逻辑不难,上面的注释已经完全说的明白了,然后我们看到最后也是来构造这个Builder对象,我们看到里面出现了一个MethodViewBinding 对象,看名字我们就能想到,这个看你的存放方法相关的一些信息的嘛:

final class MethodViewBinding implements MemberViewBinding {
  private final String name;
  private final List<Parameter> parameters;
  private final boolean required;
}

我们看到我们猜想是正确的,这个类存放了方法名,参数,和是否是required。然后最后放进builder中。到这里我们的信息收集已经说完了,下一篇我们将来说我们的代码生成,敬请期待哈。。。。
总结:ButterKnife的注解处理器主要分为信息收集和生成代码,我们这篇代码主要讲了信息收集(因为实在太多,不得不分出来),下一篇我们重点来讲生成代码,信息收集里面主要是为生成代码最准备,构造一个Builder对象,这个对象包含了生成代码需要的所有信息,大家一定记住!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • 博文出处:ButterKnife源码分析,欢迎大家关注我的博客,谢谢! 0x01 前言 在程序开发的过程中,总会有...
    俞其荣阅读 2,000评论 1 18
  • 本文主要介绍Android之神JakeWharton的一个注解框架,听说现在面试官现在面试都会问知不知道JakeW...
    Zeit丶阅读 962评论 4 6
  • butterknife注解框架相信很多同学都在用,但是你真的了解它的实现原理吗?那么今天让我们来看看它到底是怎么实...
    打不死的小强qz阅读 626评论 0 4
  • 主目录见:Android高级进阶知识(这是总目录索引) 昨天我们已经分析完butterknife的注解处理器的收集...
    ZJ_Rocky阅读 510评论 0 3
  • 醉飨野中,无人寻,千百回眸,鲜问津,踽踽而行,只为漫星挥洒,溅透衣颊。 怜嫩柳新芽,娇艳欲放,惜燕雀鸿鹄,咫尺天涯...
    RocToken阅读 210评论 0 1