Butter Knife


首先是buffer knife这个框架的优势:

1.强大的View绑定和Click事件处理功能,简化代码,提升开发效率

2.方便的处理Adapter里的ViewHolder绑定问题

3.运行时不会影响APP效率,使用配置方便

4.代码清晰,可读性强


说到这个框架的话就不得稍微在提及一下注解
注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以再源码层次上进行操作,或者可以处理编译器在其中放置了注解的的类文件。
注解不会改变程序的编译方式。Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。
为了能够受益于注解,你需要选择一个处理工具,然后向你的处理工具可以理解的代码中插入注解,之后运用该处理工具处理代码,Butter Knife就相当于这个工具。
注解本身是不会做任何事情,它需要工具支持才会有用。除了Butter Knife,像测试时使用的JUnite4测试工具也是基于注解实现的。


注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

Retention注解

Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:
1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.

Target注解

@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR)  //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///   

Butter Knife 主要使用的是编译时动态处理,通过对编译过程中的注解生成绑定所需要的函数。

所以Butter Knife的使用并不会降低软件运行时的效率,因为他的工作是在代码编译时进行的,生成了所需代码。

在java中实现注解时间处理器:

一种添加监听器的方法

mybutton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            doSomething()
        }
    });
如果使用注解的话(使用java简单解释):

@ActionListenerFor(source="myButton")

void doSomething(){....}

public class ActionListenerInstaller {
    public static void processAnnotation(Object obj)
    {
        try{
            Class<?> cl = obj.getClass();
            for (Method m : cl.getDeclaredMethods()){
                ActionListenerFor a = m.getAnnotations(ActionListenerFor.class);
                if(a != null){
                    Field f = cl.getDeclaredField(a.source);
                    f.setAccessible(true);
                    addListener(f.get(obj), obj, m);
                }
            }
        }catch (ReflectiveOperationException e)
        {
            e.printStackTrace();
        }
    }

    public static void addListener(Object source, final Object param, final Method m)throws ReflectiveOperationException{
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return m.invoke(param);
            }
        };
        Object listener = Proxy.newProxyInstance(null, new Class[]{java.awt.event.ActionListener.class}, handler);
        Method adder = source.getClass().getMethod("addActionListener", ActionListener.class);
        adder.invoke(source, listener);
    }
    }


对外注解ji


 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
    String source();
}

butterknife主要包含三部分

1.butterknife可以被调用的公用方法,这里相当于对android本身的findviewbyid等操作进行了一次封装

2.butterknife-annotation的注解接口定义

3.通过编译对butterknife的方法进行代码生成(工程代码的编译过程,也就是butterknife的运行过程,因为它是一个在编译过程中生成代码的工具。)

注解接口定义分为两部分

先是定义监听类型以及监听方法:

@Retention(RUNTIME) @Target(ANNOTATION_TYPE)
public @interface ListenerClass {
  String targetType();

  /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */
  String setter();

  /**
   * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If
   * empty {@link #setter()} will be used by default.
   */
  String remover() default "";

  /** Fully-qualified class name of the listener type. */
  String type();

  /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */
  Class<? extends Enum<?>> callbacks() default NONE.class;

  /**
   * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()}
   * and an error to specify more than one value.
   */
  ListenerMethod[] method() default { };

  /** Default value for {@link #callbacks()}. */
  enum NONE { }
}
@Retention(RUNTIME) @Target(FIELD)
public @interface ListenerMethod {
  /** Name of the listener method for which this annotation applies. */
  String name();

  /** List of method parameters. If the type is not a primitive it must be fully-qualified. */
  String[] parameters() default { };

  /** Primitive or fully-qualified return type of the listener method. May also be {@code void}. */
  String returnType() default "void";

  /** If {@link #returnType()} is not {@code void} this value is returned when no binding exists. */
  String defaultReturn() default "null";
}
然后定义具体的操作

如OnClick的操作

@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
    targetType = "android.widget.AdapterView<?>",
    setter = "setOnItemClickListener",
    type = "android.widget.AdapterView.OnItemClickListener",
    method = @ListenerMethod(
        name = "onItemClick",
        parameters = {
            "android.widget.AdapterView<?>",
            "android.view.View",
            "int",
            "long"
        }
    )
)
public @interface OnItemClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}

之后是对文件中的注解进行遍历,这里面主要的方法是

@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;
}
这个方法的作用主要是扫描、评估和处理我们程序中的注解,然后生成Java文件。这一操作主要是通过调用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 @BindBitmap element.
  for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
    if (!SuperficialValidation.validateElement(element)) continue;
    try {
      parseResourceBitmap(element, builderMap, erasedTargetNames);
    } catch (Exception e) {
      logParsingError(element, BindBitmap.class, e);
    }
  }

  • 扫描所有具有注解的类,然后根据这些类的信息生成BindingClass,最后生成以TypeElement为键,BindingClass为值的键值对。
  • 循环遍历这个键值对,根据TypeElement和BindingClass里面的信息生成对应的java类。例如AnnotationActivity生成的类即为Cliass$$ViewBinder类。

因为我们之前用的例子是绑定的一个View,所以我们就只贴了解析View的代码。好吧,这里遍历了所有带有@BindView的Element,然后对每一个Element进行解析,也就进入了parseBindView这个方法中:

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
    Set<TypeElement> erasedTargetNames) {
  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();
  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();

  BindingSet.Builder builder = builderMap.get(enclosingElement);
  QualifiedId qualifiedId = elementToQualifiedId(element, 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);
  }

  String name = simpleName.toString();
  TypeName type = TypeName.get(elementType);
  boolean required = isFieldRequired(element);

  builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

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

都是在拿到注解信息,然后验证注解的target的类型是否继承自view,然后上面这一行代码获得我们要绑定的View的id,再从targetClassMap里面取出BindingClass(这个BindingClass是管理了所有关于这个注解的一些信息还有实例本身的信息,其实最后是通过BindingClass来生成java代码的)。

猜你喜欢

转载自blog.csdn.net/u011414158/article/details/71703352