Butter Knife

来源:互联网 发布:数据建模培训班 编辑:程序博客网 时间:2024/05/29 18:10


首先是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代码的)。

1 0
原创粉丝点击