ButterKnife源码分析

11 篇文章 0 订阅

0 使用手册

http://jakewharton.github.io/butterknife/

1 使用方法简介


class ExampleActivity extends Activity {
  @InjectView(R.id.title) TextView title1;
  @InjectView(R.id.title) TextView title2;
  @InjectViews({ R.id.first_name, R.id.middle_name,R.id.last_name })
    List nameViews;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.inject(this);
    // TODO Use "injected" views...
  }
}

@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
  if (door.hasPrizeBehind()) {
    Toast.makeText(this, "You win!", LENGTH_SHORT).show();
  } else {
    Toast.makeText(this, "Try again", LENGTH_SHORT).show();
  }
}

@OnTextChanged(value=R.id.submit,callback=OnTextChanged.Callback.TEXT_CHANGED)
public void sayHi() {
  button.setText("Hello!");
}

@OnTextChanged(value=R.id.submit,callback=OnTextChanged.Callback.TEXT_CHANGED)
public void sayHello() {
  button.setText("Hello!");
}

2 基本原理

在编译之前,利用Java的预处理功能,扫描每一个有注解的类,找出类中注解的字段生成ViewBinding,每一个方法生成ListenerBinding,形成一个ViewInjector,再利用Java的Filer生成一个有注入代码的类文件,最后在执行阶段,当调用ButterKnife.inject()方法时调用生成的类文件完成注入功能。

3 数据结构分析

@ListenerClass:代表一个事件监听器,1部分中每注解一个方法都是使用了一个事件监听器,如@OnClick最后会生成一个OnClickListener对象,该注解表明了每一个方法注解所对应的事件监听器。callbacks:如果事件监听器中有多个响应方法,则通过这个字段区分,如果只有一个方法,则在method字段中注明。

@ListenerMethod:代表事件监听器中的一个方法,例如OnClickListener对象中的OnClick方法

@OnClick : 方法注解,表示需要生成对应类型的事件监听器。

@InjectView:字段注解,表示需要为该字段生成相应的注入代码

@InjectViews:数组或列表字段注解,表示需要为该字段生成响应的注入代码。

Finder:查找注解id,根据注解的id,找到响应的View。

ViewBinding:代表一个注解字段,例如1部分中的 title1,title2字段。

CollectionBinding:代表一个数组或列表注解字段,例如1部分中的nameViews字段。

ListenerBinding:代表一个注解方法,例如1部分中的sayHi() , sayHello()。

ViewInjection:代表一个注解id,将同一个id的所有注解字段,注解方法整合在一起,形成ViewInjection。

ViewInjector:代表一个注解类,例如1部分中的ExampleActivity ,每一个有注解的类都生成一个ViewInjector,在编译之前使用该ViewInjector,为每一个类生成XXXX$$ViewInjector,生成响应的注入代码

4 代码分析

ButterKnife.inject(Object target, Object source, Finder finder)
  //加载编译前生成的XXXX$$ViewInjector类,调用该类的inject方法,该inject方法利用finder根据注解中的id查找到响应的View,为该view设置事件监听器,再为target注入该view。
  static void inject(Object target, Object source, Finder finder) {
    Class<?> targetClass = target.getClass();
    try {
      if (debug) Log.d(TAG, "Looking up view injector for " + targetClass.getName());
      Injector<Object> injector = findInjectorForClass(targetClass);
      if (injector != null) {
        injector.inject(finder, target, source);
      }
    } catch (Exception e) {
      throw new RuntimeException("Unable to inject views for " + targetClass.getName(), e);
    }
  }
ButterKnifeProcessor.process
 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
         //先生成ViewInjector
    Map<TypeElement, ViewInjector> targetClassMap = findAndParseTargets(env);
        //使用ViewInjector动态生成XXXX$$ViewInjector.java代码
    for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      ViewInjector viewInjector = entry.getValue();

      try {
        JavaFileObject jfo = filer.createSourceFile(viewInjector.getFqcn(), typeElement);
        Writer writer = jfo.openWriter();
        writer.write(viewInjector.brewJava());
        writer.flush();
        writer.close();
      } catch (IOException e) {
        error(typeElement, "Unable to write injector for type %s: %s", typeElement, e.getMessage());
      }
    }

    return true;
  }
ButterKnife.findAndParseTargets
private Map<TypeElement, ViewInjector> findAndParseTargets(RoundEnvironment env) {

      //处理@InjectView;
    for (Element element : env.getElementsAnnotatedWith(InjectView.class)) {
      try {
        parseInjectView(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        StringWriter stackTrace = new StringWriter();
        e.printStackTrace(new PrintWriter(stackTrace));

        error(element, "Unable to generate view injector for @InjectView.\n\n%s", stackTrace);
      }
    }

      //处理@InjectViews;
    // Process each @InjectViews element.
    for (Element element : env.getElementsAnnotatedWith(InjectViews.class)) {
      try {
        parseInjectViews(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        StringWriter stackTrace = new StringWriter();
        e.printStackTrace(new PrintWriter(stackTrace));

        error(element, "Unable to generate view injector for @InjectViews.\n\n%s", stackTrace);
      }
    }
      //处理Listener注解;
    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }

      //设置父类Injector
    for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) {
      String parentClassFqcn = findParentFqcn(entry.getKey(), erasedTargetNames);
      if (parentClassFqcn != null) {
        entry.getValue().setParentInjector(parentClassFqcn + SUFFIX);
      }
    }

    return targetClassMap;
  }
ButterKnife.parseInjectView
private void parseInjectView(Element element, Map<TypeElement, ViewInjector> targetClassMap,
      Set<String> erasedTargetNames) {
    boolean hasError = false;
      //获取字段所在的类
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
      //判断是否是View的子类,或者接口
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      error(element, "@InjectView fields must extend from View or be an interface. (%s.%s)",
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

      //判断字段和所在的类不能是Private的
    // Verify common generated code restrictions.
    hasError |= isInaccessibleViaGeneratedCode(InjectView.class, "fields", element);
      //判断不是java和android内部类
    hasError |= isBindingInWrongPackage(InjectView.class, element);

    // Check for the other field annotation.
    if (element.getAnnotation(InjectViews.class) != null) {
      error(element, "Only one of @InjectView and @InjectViews is allowed. (%s.%s)",
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    // Assemble information on the injection point.
    int id = element.getAnnotation(InjectView.class).value();

     //判断该id仅出现了一次
    ViewInjector injector = targetClassMap.get(enclosingElement);
    if (injector != null) {
      ViewInjection viewInjection = injector.getViewInjection(id);
      if (viewInjection != null) {
        Iterator<ViewBinding> iterator = viewInjection.getViewBindings().iterator();
        if (iterator.hasNext()) {
          ViewBinding existingBinding = iterator.next();
          error(element,
              "Attempt to use @InjectView for an already injected ID %d on '%s'. (%s.%s)", id,
              existingBinding.getName(), enclosingElement.getQualifiedName(),
              element.getSimpleName());
          return;
        }
      }
    }

      //生成一个viewInjector和viewBinding
    String name = element.getSimpleName().toString();
    String type = elementType.toString();
    boolean required = element.getAnnotation(Optional.class) == null;

    ViewInjector viewInjector = getOrCreateTargetClass(targetClassMap, enclosingElement);
    ViewBinding binding = new ViewBinding(name, type, required);
    viewInjector.addView(id, binding);

    // Add the type-erased version to the valid injection targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }
ButterKnife.parseInjectViews
private void parseInjectViews(Element element, Map<TypeElement, ViewInjector> targetClassMap,
      Set<String> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the type is a List or an array.
      //判断注解的是List或者数组
    TypeMirror elementType = element.asType();
    String erasedType = doubleErasure(elementType);
    TypeMirror viewType = null;
    CollectionBinding.Kind kind = null;
    if (elementType.getKind() == TypeKind.ARRAY) {
      ArrayType arrayType = (ArrayType) elementType;
      viewType = arrayType.getComponentType();
      kind = CollectionBinding.Kind.ARRAY;
    } else if (LIST_TYPE.equals(erasedType)) {
      DeclaredType declaredType = (DeclaredType) elementType;
      List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
      if (typeArguments.size() != 1) {
        error(element, "@InjectViews List must have a generic component. (%s.%s)",
            enclosingElement.getQualifiedName(), element.getSimpleName());
        hasError = true;
      } else {
        viewType = typeArguments.get(0);
      }
      kind = CollectionBinding.Kind.LIST;
    } else {
      error(element, "@InjectViews must be a List or array. (%s.%s)",
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }
      //???????
    if (viewType instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) viewType;
      viewType = typeVariable.getUpperBound();
    }

      //判断是否是View的之类,或者接口
    // Verify that the target type extends from View.
    if (viewType != null && !isSubtypeOfType(viewType, VIEW_TYPE) && !isInterface(viewType)) {
      error(element, "@InjectViews type must extend from View or be an interface. (%s.%s)",
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }
    //判断字段和所在的类不能是Private的
      //判断不是java和android内部类
    // Verify common generated code restrictions.
    hasError |= isInaccessibleViaGeneratedCode(InjectViews.class, "fields", element);
    hasError |= isBindingInWrongPackage(InjectViews.class, element);

    if (hasError) {
      return;
    }

    // Assemble information on the injection point.
    String name = element.getSimpleName().toString();
    int[] ids = element.getAnnotation(InjectViews.class).value();
    if (ids.length == 0) {
      error(element, "@InjectViews must specify at least one ID. (%s.%s)",
          enclosingElement.getQualifiedName(), element.getSimpleName());
      return;
    }

    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(element, "@InjectViews annotation contains duplicate ID %d. (%s.%s)", duplicateId,
          enclosingElement.getQualifiedName(), element.getSimpleName());
    }

    assert viewType != null; // Always false as hasError would have been true.
    String type = viewType.toString();
    boolean required = element.getAnnotation(Optional.class) == null;

    //生成一个viewInjector和CollectionBinding
    ViewInjector viewInjector = getOrCreateTargetClass(targetClassMap, enclosingElement);
    CollectionBinding binding = new CollectionBinding(name, type, kind, required);
    viewInjector.addCollection(ids, binding);

    erasedTargetNames.add(enclosingElement.toString());
  }
Butterknife.parseListenerAnnotation
private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
      Map<TypeElement, ViewInjector> targetClassMap, Set<String> 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()));
    }

    ExecutableElement executableElement = (ExecutableElement) element;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Assemble information on the injection point.
    Annotation annotation = element.getAnnotation(annotationClass);
    Method annotationValue = annotationClass.getDeclaredMethod("value");
    if (annotationValue.getReturnType() != int[].class) {
      throw new IllegalStateException(
          String.format("@%s annotation value() type not int[].", annotationClass));
    }

      //获取该注解上的所有View的id
    int[] ids = (int[]) annotationValue.invoke(annotation);
    String name = executableElement.getSimpleName().toString();
    boolean required = element.getAnnotation(Optional.class) == null;

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

    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 == View.NO_ID) {
        if (ids.length == 1) {
          if (!required) {
            error(element, "ID free injection must not be annotated with @Optional. (%s.%s)",
                enclosingElement.getQualifiedName(), element.getSimpleName());
            hasError = true;
          }

          // Verify target type is valid for a binding without an id.
          String targetType = listener.targetType();
          if (!isSubtypeOfType(enclosingElement.asType(), targetType)
              && !isInterface(enclosingElement.asType())) {
            error(element, "@%s annotation without an ID may only be used with an object of type "
                    + "\"%s\" or an interface. (%s.%s)",
                    annotationClass.getSimpleName(), targetType,
                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;
    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) {
        //ListenerClass中如果填充的是method,则保证只有一个
      if (listener.callbacks() != ListenerClass.NONE.class) {
        throw new IllegalStateException(
            String.format("Both method() and callback() defined on @%s.",
                annotationClass.getSimpleName()));
      }
      method = methods[0];
    } else {
        //ListenerClass中如果没有填充method,则取callback
      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()));
      }
    }

    //527-548行确保注解的方法参数与返回值,跟注解中ListenerClass保存的方法参数和返回值一致
    // Verify that the method has equal to or less than the number of parameters as the listener.
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    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.
    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;
    }

    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])
              || isInterface(methodParameterType)) {
            parameters[i] = new Parameter(j, methodParameterType.toString());
            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;
        }
      }
    }

    ListenerBinding binding = new ListenerBinding(name, Arrays.asList(parameters), required);
    ViewInjector viewInjector = getOrCreateTargetClass(targetClassMap, enclosingElement);
    for (int id : ids) {
      if (!viewInjector.addListener(id, 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 injection targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }
ViewInjector.emitInject

生成inject方法

  private void emitInject(StringBuilder builder) {
    builder.append("  @Override ")
        .append("public void inject(final Finder finder, final T target, Object source) {\n");

    // Emit a call to the superclass injector, if any.
    if (parentInjector != null) {
      builder.append("    super.inject(finder, target, source);\n\n");
    }

    // Local variable in which all views will be temporarily stored.
    builder.append("    View view;\n");

    //生成字段注入代码和事件监听器注入代码
    for (ViewInjection injection : viewIdMap.values()) {
      emitViewInjection(builder, injection);
    }

    //生成数组注入代码
    for (Map.Entry<CollectionBinding, int[]> entry : collectionBindings.entrySet()) {
      emitCollectionBinding(builder, entry.getKey(), entry.getValue());
    }

    builder.append("  }\n");
  }
ViewInjector.emitViewBindings

生成的代码类似于

view = finder.findRequiredView(source, 2131361935, "field 'title1;'");
target.title1 = (android.widget.TextView) view;
  private void emitViewBindings(StringBuilder builder, ViewInjection injection) {
    Collection<ViewBinding> viewBindings = injection.getViewBindings();
    if (viewBindings.isEmpty()) {
      return;
    }

    for (ViewBinding viewBinding : viewBindings) {
      builder.append("    target.")
          .append(viewBinding.getName())
          .append(" = ");
      if (viewBinding.requiresCast()) {
        builder.append("finder.castView(view")
            .append(", ")
            .append(injection.getId())
            .append(", \"");
        emitHumanDescription(builder, viewBindings);
        builder.append("\");\n");
      } else {
        builder.append("view;\n");
      }
    }
  }
ButterKnife.emitListenerBindings

生成事件监听器的注入代码,生成的代码类似于

view = finder.findRequiredView(source, 2131361935, "field 'title1' and method 'sayHi'");
target.title1 = (android.widget.TextView) view;
((android.widget.TextView) view).addTextChangedListener(
      new android.text.TextWatcher() {
        @Override public void onTextChanged(
          java.lang.CharSequence p0,
          int p1,
          int p2,
          int p3
        ) {
          target.sayHi();
        }
        @Override public void beforeTextChanged(
          java.lang.CharSequence p0,
          int p1,
          int p2,
          int p3
        ) {

        }
        @Override public void afterTextChanged(
          android.text.Editable p0
        ) {

        }
      });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值