首先是buffer knife这个框架的优势:
1.强大的View绑定和Click事件处理功能,简化代码,提升开发效率
2.方便的处理Adapter里的ViewHolder绑定问题
3.运行时不会影响APP效率,使用配置方便
4.代码清晰,可读性强
注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面:
- 生成文档,通过代码里标识的元数据生成javadoc文档。
- 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
- 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
- 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
Retention注解
Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:
1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
Target注解
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代码的)。