编译时注解Butterknife源码解析之深入篇(雷惊风)

1.概述。

上篇文章我对Butterknife实现做了一些基础的说明,本篇文章我将向大家详细分析@BindView、@OnClick解析流程、生成BindingSet对应Java文件流程及我们调用ButterKnife.bind(this)后ButterKnife与生成Java文件的建立连接过程。

2.@BindView解析流程。

这篇文章将接着上篇文章的findAndParseTargets(RoundEnvironment env)方法,如果你看了上篇文章,那么你就知道,所有Butterknife中注解的解析都是在这个方法中的。好的那么我们开始吧。我们这个方法的流程上篇文章已经说过了,这里就不再浪费口水了,我们把解析@BindView的代码拷出来再看一下:

// Process each @BindView element.
/**
 * 处理BindView(R.id.btn)注解;
 * 1.做了一系列验证;如:private static修饰符判断、@BindView必须作用在View上或者interface上等等;
 * 2.id的处理;生成QualifiedId、缓存等;
 * 3.builder创建;一个id是否注解了多次
 * 4.@Nullable处理,创建FieldViewBinding存入builder,将enclosingElement存入erasedTargetNames;
 */
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);
    }
}

 很简单,获取了项目中所有的被@BindView注解了的Element,通过for()循环处理每一个,一个try{}catch{}代码块,try中出问题,打印信息,关于在注解处理器中处理错误log信息,在这里就不讲了,可以在init()方法中获取Messager辅助类。看来重点在parseBindView(element, builderMap, erasedTargetNames);方法中了,跟进去:

parseBindView(element, builderMap, erasedTargetNames);方法中了,跟进去:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
                           Set<TypeElement> erasedTargetNames) {
    //获取父级Element;
    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();
    //@BindView必须作用在View上或者interface上;
    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.
    int id = element.getAnnotation(BindView.class).value();

    //通过enclosingElement获取builder,每一个builder对应一个类,如activity;
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    //将element所在包与id封装到QualifiedId中;
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
        //判断当前@BindView所修饰控件是否已经绑定过;
        //getId():将id存入Id对象,并存入symbols;
        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;
        builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    //判断是否添加了@Nullable
    boolean required = isFieldRequired(element);

    //通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding)
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

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

方法也还好,不到100行,来看看在这个方法中做了一些什么操作吧。咱们先拿出一部分来看:

//获取父级Element;
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();
//@BindView必须作用在View上或者interface上;
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;
}

这部分是对我们@BindView注解应用的一个正确性的一个检查,首先获取了我们的element对应的外部类的TypeElement,比如,activity中用@BindView注解了一个Button btn;获取了activity对应的TypeElement,如果你看了上篇文章你就会明白。然后调用了一个isInaccessibleViaGeneratedCode(BindView.class,"fields", element)方法与一个isBindingInWrongPackage(BindView.class, element)方法进行判断操作。一个一个来看一下,第一个:

/**
 * 检查annotation作用域是否正确;
 *
 * @param annotationClass
 * @param targetThing
 * @param element
 * @return
 */
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
                                               String targetThing, Element element) {
    boolean hasError = false;
    //获取当前element所在类的TypeElement;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify method modifiers.获取当前element的修饰符;
    Set<Modifier> modifiers = element.getModifiers();
    //修饰符不能是private或者static的,否则报告异常(error方法);
    // Messager提供给注解处理器一个报告错误、警告以及提示信息的途径。
    // 它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。
    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.element只能直接从属于类(不能修饰局部变量);否则报错;
    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;
    }

    // Verify containing class visibility is not private.element外层类不能是私有的,否则报错;
    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;
}

在这个类中判断了三种非正常情况:

1.@BindView注解的Element为private或者static修饰报错,如下例子:

@BindView(R.id.btn)
  private Button btn;

这种情况下就会报错。

2.当前Element不是直接在一个类里边,报错。

   如:在一个方法的局部变量上添加了注解。

3.外部Elementprivate的报错。如:

private class Activity extends ...{
  @BindView(R.id.btn)
  Button btn;
}

再看一下isBindingInWrongPackage(BindView.class, element)方法:

/**
 * 检查anitation注解是否作用在了系统类上;
 *
 * @param annotationClass
 * @param element
 * @return
 */
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;
}

 这个方法主要是检查我们是否用到了系统类上,这里注意我们自己定义的包名。接着回到上一个方法向下看,后续又检查了是否用在了View子类上或者Interface上。我都加了注释,不在详细解释。如果上边检查有一步出问题,则return终止。到这里,检查我们应用@BindView合法性就完了。再往下边走:

// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();

//通过enclosingElement获取builder,每一个builder对应一个类,如activity;
BindingSet.Builder builder = builderMap.get(enclosingElement);
//将element所在包与id封装到QualifiedId中;
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
    //判断当前@BindView所修饰控件是否已经绑定过;
    //getId():将id存入Id对象,并存入symbols;
    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;
    builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}

首先获取到我们注解中指定的id,如下代码中的R.id.btn:

@BindView(R.id.btn)
Button btn;

查看builderMap中是否已经缓存了外部Element对应的BindingSet.Builder,通过id创建QualifiedId看一下这个过程:

private QualifiedId elementToQualifiedId(Element element, int id) {
    return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);
}

这里用到了辅助类elementUtils获取element的包名,通过包名与id创建了QualifiedId

后边是builder判断,如果不为空,通过QualifiedId生成Id判断是否注解过相同id,注解过,则输出错误信息。Builder为空,调用getOrCreateBindingBuilder(builderMap, enclosingElement)创建或获取builder,看一下:

private BindingSet.Builder getOrCreateBindingBuilder(
        Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
        //生成一个builder,
        // builder中保存了泛型信息、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部;
        builder = BindingSet.newBuilder(enclosingElement);
        builderMap.put(enclosingElement, builder);
    }
    return builder;
}

如果我们的builderMap中已经存在,直接返回,不存在调用BindingSet.newBuilder()创建并保存到builderMap中。创建保存这步是关键,看一下:

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);
  //参数:泛型、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部;
  return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

首先判断了我们外部类Element是不是View是不是Activity是不是Dialog,进行了泛型判断处理,通过包名与类名得到我们将要创建的Java类的名称,有没有final修饰,然后通过以上判断的结果生成Builder对象返回。这样我们就创建了一个BindingSet.Builder对象,其中包含是不是ViewactivityDialog,将来要生成的Java类名,有没有final修饰,泛型信息。再次回到parseBindView()方法中看剩余部分:

String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
//判断是否添加了@Nullable
boolean required = isFieldRequired(element);

//通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding)
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

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

获取注解的element的name(上边例子中的btn)与type(上边例子中的Button),判断了是否添加了@Nullable注解,通过这三个内容生成FieldViewBinding(与MethodViewBinding都继承了MemberViewBinding,封装一个最基础的@BindView注解的element对象,)对象,调用builder的addField()方法将Id对象与FieldViewBinding对象传入,进入BindingSet的Buidler看一下:

void addField(Id id, FieldViewBinding binding) {
  getOrCreateViewBindings(id).setFieldBinding(binding);
}
又调用了getOrCreateViewBindings()方法,跟:
private ViewBinding.Builder getOrCreateViewBindings(Id id) {
  ViewBinding.Builder viewId = viewIdMap.get(id);
  //创建针对当前id的ViewBinding.Builder;
  if (viewId == null) {
    viewId = new ViewBinding.Builder(id);
    viewIdMap.put(id, viewId);
  }
  return viewId;
}

   通过查看缓存中是否已经保存了当前id对应的ViewBinding.Builder有返回,没有创建并缓存。在这里通过id创建了ViewBinding.Builder对象。回到addField()中,调用创建好的ViewBinding.Builder对象的setFieldBingding()方法将FieldViewBinding对象保存。这里的层次你得搞清楚了。FieldViewBinding保存在ViewBinding.Builder对象中,ViewBinding.Builder对象是在BindingSet.Builder对象调用addField()方法时创建的。回到parseBindView()方法,还剩一句代码,就是将父类的elementType保存到一开始的erasedTargetNames中。好,我们的parseBindView()方法就分析完了。同样,我们解析@BindView注解逻辑也就分析完了,你弄明白了吗,不明白也没关系,我做一下总结:

1.首先就是一系列判断我们应用@BindView注解的正确性;

2.然后获取id,获取外部类ElementType对应的BindingSet.Builder对象是否存在,存在判断当前id是否已经注解过。不存在,创建。创建的过程中获取了类是否为View、activity、Dialog,是否final,泛型,将生成的类名等。保存到builderMap中。

3.调用BindingSet.Builder对象的addField()方法,过程中创建了与之对应的FieldViewBinding对象、ViewBinding.Buidler对象,并将部分信息进行缓存。

4.将外部类ElementType保存到erasedTargetNames中。

这就是解析@ViewBind注解的整个过程。

   下边说一下个人的理解,如果一个类(如Activity)里边有@ViewBind注解的控件,那么就会对应这个类生成一个BindingSet.Builder,每一个@ViewBind注解的id会对应生成一个ViewBinding.Builder对象,一个类里边的所有生成的ViewBinding.Builder对象会根据id保存到BindingSet中的viewIdMap中。ViewBinding.Builder中保存了自己的FieldViewBinding对象。最终就是把生成的ViewBinding.Builder对象与外部类的ElementType对应着保存到了builderMap中,将外部类的ElementType保存到erasedTargetNames中。好了,关于@ViewBind的解析就分析到这里。

3.@OnClick注解解析过程分析

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

下边看一下ButterKnife的事件处理流程,通过上边的代码可以看出来,所有的事件注解处理流程都是一样的,看一下for循环中方法,这里传入的是辅助对象env,当前要处理的事件注解类listener,还有就是builderMap,erasedTargetNames,我们进入findAndParseListener()方法,看一下:

/**
 * 传入一种事件注解进行处理;
 *
 * @param env
 * @param annotationClass
 * @param builderMap
 * @param erasedTargetNames
 */
private void findAndParseListener(RoundEnvironment env,
                                  Class<? extends Annotation> annotationClass,
                                  Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
    //获取被某个事件注解的每一个element;
    for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
        //google封装的方法验证element合法性;
        if (!SuperficialValidation.validateElement(element)) continue;
        try {
            //anotationClass是@@OnClickListener;element是被其注解的方法类型的element;
            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(annotationClass, element, builderMap, erasedTargetNames);这个方法比较长,这里就不贴了,首先还是判断合法性,这部分代码不太重要。

//反射方式获取id数组;
int[] ids = (int[]) annotationValue.invoke(annotation);
...//省略部分合法行应用代码;
//生成类里边重写父类的方法;如@OnClick中的doClick();
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) {
    //@OnClick等都是一个;
    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处理;@OnItemSelected、@OnPageChange、@OnTextChanged
    Method annotationCallback = annotationClass.getDeclaredMethod("callback");
    Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
    Field callbackField = callback.getDeclaringClass().getField(callback.name());
    method = callbackField.getAnnotation(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()));
    }
}

上边代码主要是获取了在定义事件注解时定义的方法信息,这里看一下我们的@OnClick注解是怎么定义的:

@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
    targetType = "android.view.View",
    setter = "setOnClickListener",
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}

以上代码主要是获取的这个method就是@OnClick注解类中的@ListenerClass 中的@ListenerMethod中的doClick。接着去代码里找关键代码:

/**
 * 被注解事件注解的方法参数与jack在注解事件中定义的方法参数做匹配处理;如simple中的onItemClick()参数,如下代码;
 *   @OnItemClick(R2.id.list_of_things)
 *   void onItemClick(int position) {
 *     Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
 *    }
 */

Parameter[] parameters = Parameter.NONE;
//我们的方法中参数不为空,开始处理;
if (!methodParameters.isEmpty()) {
    parameters = new Parameter[methodParameters.size()];
    //BitSet常见的应用是那些需要对海量数据进行一些统计工作的时候,比如日志分析等
    BitSet methodParameterUsed = new BitSet(methodParameters.size());
    //parameterTypes为jack注解中定义的参数;methodParameters为我们的参数
    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;
            }
        }
        //参数匹配错误处理;
...//这里省略掉错误处理
            }
}

以上代码主要是获取我们在用@OnClick等注解时方法的参数与定义@OnClick注解时规定参数的一个对应处理,最终保存到parameters中,已经添加注释,不过多解释,接着往下看:

//public void onClick(){};name相当于onClick;
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
//获取注解外部类对应的Builder对象;与@BindView中一样;
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
for (int id : ids) {
    QualifiedId qualifiedId = elementToQualifiedId(element, id);

    /**
     * 是否添加到了对应的ViewBinding.Builder中,没有创建,添加;
     *
     *  参数:Id:通过QualifiedId生成的Id对象;
     *        listener:注解中的@ListenerClass注解信息
     *        method:生成类里边重新父类的方法;如@OnClick中的doClick();
     *        binding:生成的包含name/参数/判断有无@Optional 注解的       *MethodViewBinding对象;
     */
    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);

创建了MethodViewBinding对象,保存name、参数及是否有@Optional注解。调用了在@BindView中也用到的getOrCreateBindingBuilder(builderMap, enclosingElement);
方法获取BindingSet.Builder对象;循环每一个id创建对应的QualifiedId对象,调用builder.addMethod()方法,这里参数已经在代码中注释,看一下这个方法:

boolean addMethod(
                Id id,
                ListenerClass listener,
                ListenerMethod method,
                MethodViewBinding binding) {
            ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
            //ViewBinding.Builder 中的methodBindings中是否已经有了对应的方法;
            if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {
                return false;
            }
            viewBinding.addMethodBinding(listener, method, binding);
            return true;
        }

同样也调用了getOrCreateViewBindings(id)方法获取ViewBinding.Builder;最后将listener等信息传入viewBinding.addMethodBinding()方法。进入ViewBinding看一下:
public void addMethodBinding(ListenerClass listener, ListenerMethod method,
                                     MethodViewBinding binding) {
            //methods为一种事件方法;如:OnPageChangeListener、OnTextChangeListener中的一种
            Map<ListenerMethod, Set<MethodViewBinding>> methods = methodBindings.get(listener);
            //set为每一种事件下边的多个小事件;如acitivity中可能会注解
            // @OnTextChanged(value = R.id.mEditV,callback =BEFORE_TEXT_CHANGED);
            //@OnTextChanged(value = R.id.mEditV,callback = TEXT_CHANGED);
            //@OnTextChanged(value = R.id.mEditV,callback = AFTER_TEXT_CHANGED);
            // 每一次是一个;
            Set<MethodViewBinding> set = null;
            if (methods == null) {
                methods = new LinkedHashMap<>();
                methodBindings.put(listener, methods);
            } else {
                set = methods.get(method);
            }
            if (set == null) {
                set = new LinkedHashSet<>();
                methods.put(method, set);
            }
            set.add(binding);
        }

里边也有缓存,有注释,就不说了,有了前边对@BindView的了解,这个就省不少事了,说白了,还是在getOrCreateBindingBuilder(builderMap, enclosingElement)这个方法中保存builderMap,最后保存erasedTargetNames.add(enclosingElement);这里有不理解的可以下载加好注释的源码多看几遍,最后会有下载地址。

到现在为止,我们关于注解解析成BindingSet.Builder对象就完了。回到我们解析事件注解的开始ButterKnifeProcessor的findAndParseTargets(RoundEnvironment env)方法,

看剩余部分,也就是将BindingSet.Builder对象生成BindingSet的过程。看代码吧:

// 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).
//builderMap===(EnclosingElement,BindingSet.Builder)
//父类的处理,生成BindingSet存入bindingMap返回;
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();

    //(EnclosingElement,BindingSet.Builder)
    TypeElement type = entry.getKey();
    BindingSet.Builder builder = entry.getValue();

    //erasedTargetNames里是否保存了type的父类,有直接返回,没有返回null;
    TypeElement parentType = findParentType(type, erasedTargetNames);
    // erasedTargetNames里没有保存type父类;
    if (parentType == null) {
        bindingMap.put(type, builder.build());
    } else {
        //erasedTargetNames保存了type父类;
        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.put(type, builder.build())这句,builder.Build()方法中创建了BindingSet对象,进去看看:

BindingSet build() {
    ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
    //viewIdMap在addMethod、addField是添加数据;每个id对应的builder;
    for (ViewBinding.Builder builder : viewIdMap.values()) {
      //每一个id生成ViewBinding(id, methodBindings, fieldBinding)保存到ViewBindings中;
      viewBindings.add(builder.build());
    }
    return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
        viewBindings.build(), collectionBindings.build(), resourceBindings.build(),
        parentBinding);
  }
}

这里重点是通过我们之前生成的所有信息new出我们的BindingSet对象返回。最终存到我们ButterKnifeProcessor中findAndParseTargets(RoundEnvironment env)方法的bindingMap对象中,再将bindingMap返回到最初的process(Set<?extends TypeElement> elements, RoundEnvironment env)方法中,进行后续处理,那就让我们看看吧:

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //注解的一系列处理,最终返回bindingMap;
    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;
}

可以看到后边就是循环取出bindingMap中的每一个,调用binding.brewJava(sdk)方法,那先让咱们看看这个方法:

/**
 * 创建JavaFile;
 * @param sdk
 * @return
   */
JavaFile brewJava(int sdk) {
  //包名,及要生成的文件内容;
  return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
          //类上边的注释;
      .addFileComment("Generated code from Butter Knife. Do not modify!")
      .build();
}

最终调用了JavaFilebuilder()方法,JavaFileJavapoet包里边的类,就不说了。看一下它的builder方法的参数,第一个看方法也知道包名,第二个是啥?看看:

/**
 * 创建绑定类;
 * @param sdk
 * @return
   */
private TypeSpec createType(int sdk) {
  TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
      .addModifiers(PUBLIC);
  if (isFinal) {
    result.addModifiers(FINAL);
  }

  //如果有parentBinding则不继承Unbinder;
  if (parentBinding != null) {
    result.superclass(parentBinding.bindingClassName);
  } else {
    result.addSuperinterface(UNBINDER);
  }

  //注解了成员变量或者方法添加target成员变量;对应例子中---》private SimpleActivity target;;
  if (hasTargetField()) {
    result.addField(targetTypeName, "target", PRIVATE);
  }

  //根据不同情况添加单参数构造函数;
  if (isView) {
    result.addMethod(createBindingConstructorForView());
  } else if (isActivity) {
    result.addMethod(createBindingConstructorForActivity());
  } else if (isDialog) {
    result.addMethod(createBindingConstructorForDialog());
  }
  if (!constructorNeedsView()) {
    // Add a delegating constructor with a target type + view signature for reflective use.
    result.addMethod(createBindingViewDelegateConstructor());
  }
  //生成最终绑定的构造函数;
  result.addMethod(createBindingConstructor(sdk));

  //生成unbind()方法;
  if (hasViewBindings() || parentBinding == null) {
    result.addMethod(createBindingUnbindMethod(result));
  }

  return result.build();
}

噢,my god,都是Javapoet中的东西,没错,这里就是文件内容操作了,这里我说几个里边用到的类方法吧。详细每一行代码的意思就不说了:

TypeSpec.classBuilder-------创建类相关;

.addModifiers(PUBLIC)-------添加修饰符;

Result.superclass-----------设置父类;

result.addSuperinterface----添加接口;

result.addField-------------添加成员变量;

result.addMethod------------类添加方法或构造函数;

MethodSpec.constructorBuilder()---构造函数相关;

constructor.addParameter----构造函数添加参数;

constructor.addAnnotation---构造函数添加注解;

.addStatement---------------添加代码;

.......等等吧。

关键的生成代码是这句:

//生成最终绑定的构造函数;
result.addMethod(createBindingConstructor(sdk));

所有的绑定代码,事件处理代码生成都是在这句里边,代码就不贴了啊,篇幅有点大了,想看的可以下载我标好注释的代码,我只把sample里边activity生成的对应文件贴一下吧,大家看一下:

// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;

import android.content.Context;
import android.content.res.Resources;
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 com.example.butterknife.R;
import java.lang.IllegalStateException;
import java.lang.Override;

public class SimpleActivity_ViewBinding implements Unbinder {
  private SimpleActivity target;

  private View view2130968578;

  private View view2130968579;

  @UiThread
  public SimpleActivity_ViewBinding(SimpleActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public SimpleActivity_ViewBinding(final SimpleActivity 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);
    view2130968578 = 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);
    view2130968579 = 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'"));

    Context context = source.getContext();
    Resources res = context.getResources();
    target.butterKnife = res.getString(R.string.app_name);
    target.fieldMethod = res.getString(R.string.field_method);
    target.byJakeWharton = res.getString(R.string.by_jake_wharton);
    target.sayHello = res.getString(R.string.say_hello);
  }

  @Override
  @CallSuper
  public void unbind() {
    SimpleActivity 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;

    view2130968578.setOnClickListener(null);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;
  }
}

大家可以对着生成的Java文件去看生成Java文件过程的代码,这样便于理解,可以看到它的全部操作都是在构造函数里边做的,在构造函数里边有一个,这里咱们看一下Utils. findRequiredViewAsType()方法:

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
                                           Class<T> cls) {
    //绑定View;findViewById(R.id.btn);
    View view = findRequiredView(source, id, who);
    //强转View;如:(Button)findViewById(R.id.btn);
    return castView(view, id, who, cls);
}

public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
        return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
            + name
            + "' with ID "
            + id
            + " for "
            + who
            + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
            + " (methods) annotation.");
}

终于看到了我们的findViewById()方法,返回了获取的view,在调用castView(view, id, who, cls)方法:

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
        return cls.cast(view);
    } catch (ClassCastException e) {
        String name = getResourceEntryName(view, id);
        throw new IllegalStateException("View '"
                + name
                + "' with ID "
                + id
                + " for "
                + who
                + " was of the wrong type. See cause for more info.", e);
    }
}

进行类型转换,如将View转换成Button,就是进行(Button)findViewById(id)操作。好了,回到我们process()方法:

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

  最终拿到了javaFile,通过辅助类filer生成文件。到这里,我们的文件生成工作就完了。也就是说,我们编译期的所有工作就完成了,之前,所有的操作都是为了生成我们的这些Java文件,生成的Java文件会与我们所写的Activity等Java文件一同编译成.class文件,所以我们在Activity中调用ButterKnife.bind(this)方法才有作用。本篇文章写到现在也已经进行了四分之三了,还剩下的就是我们调用ButterKnife.bind(this)方法后的处理了,这部分就简单了,用buns eyes想想也知道是怎么搞的,为了让大家更好的了解,咱们还是看一看吧。

4.我们Activity中调用ButterKnife.bind(this)后... ...

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
  View sourceView = target.getWindow().getDecorView();
  return createBinding(target, sourceView);
}
获取了decorView根布局,调用createBinding()方法:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
  Class<?> targetClass = target.getClass();
  if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
  Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

  if (constructor == null) {
    return Unbinder.EMPTY;
  }

  //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
  try {
    return constructor.newInstance(target, source);
  } catch (IllegalAccessException e) {
    throw new RuntimeException("Unable to invoke " + constructor, e);
  } catch (InstantiationException e) {
    throw new RuntimeException("Unable to invoke " + constructor, e);
  } catch (InvocationTargetException e) {
    Throwable cause = e.getCause();
    if (cause instanceof RuntimeException) {
      throw (RuntimeException) cause;
    }
    if (cause instanceof Error) {
      throw (Error) cause;
    }
    throw new RuntimeException("Unable to create binding instance.", cause);
  }
}

上边代码中调用了一个findBindingConstructorForClass方法返回了一个constructor对象,后边调用这个对象的newInstance()方法,new了一个它的实例,最终返回,那么,我们猜测这里肯定是获取了当前Acitivity对应的在编译器生成的那个添加了绑定事件等信息的类的构造函数,看一下关键代码findBindingConstructorForClass(targetClass)

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
  Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
  if (bindingCtor != null) {
    if (debug) Log.d(TAG, "HIT: Cached in binding map.");
    return bindingCtor;
  }
  String clsName = cls.getName();
  if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
    if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
    return null;
  }
  try {
    Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
    //noinspection unchecked
    bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
  } catch (ClassNotFoundException e) {
    if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
    bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
  } catch (NoSuchMethodException e) {
    throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
  }
  BINDINGS.put(cls, bindingCtor);
  return bindingCtor;
}

看见没,看见没,

Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
    //noinspection unchecked
    bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);

对应到我们生成代码里就是下面这句:

@UiThread
public SimpleActivity_ViewBinding(final SimpleActivity target, View source){... ...}

   就这里,好了到这里,你应该明白整个ButterKnife工作原理了吧,我在最后总结一下:首先ButterKnife在编译器通过ButterKnifeProcessor类获取每一个ButterKnife支持的注解类型,通过他获取每一个添加该注解的element,经过一系列做操作,每一个添加了注解的类最终对应生成BindingSet.Builder对象,再生成BindingSet,最后根据BindingSet生成对应的Java类。我们在Activity里边调用bind()方法就是通过当前Activity找到对应的Java生成类并获取构造方法生成实例对象,在构造方法了进行了绑定操作。啊...终于说完了,思路很清晰,实现很麻烦。在此膜拜并感谢Jack大神为我们造出了这么牛逼的利器。好了,这篇文章就写到这里吧。不明白,就照着我添加好注释的源码看,应该很快就明白了,下边就是源码下载地址。谢谢!

源码下载地址:http://download.csdn.net/detail/liuyonglei1314/9773886

猜你喜欢

转载自blog.csdn.net/liuyonglei1314/article/details/60871689