ButterKnife是经过注解进行动态注入的,若是你对注解还不太了解的话,能够先看上篇有关注解的文章注解。java
ButterKnife的工做原理通常分为两步:app
首先来看最基本的注解类:ide
@Retention(CLASS) @Target(FIELD) public @interface Bind { /** View ID to which the field will be bound. */ int[] value(); }
这个就是最经常使用的Bind注解,通常用它来标识要绑定的view,在这里能够清楚的看到,这个view是在编译的时候起做用的,而且它只能用来标识成员变量。这就表示咱们能够在编译的时候解析它,从而生成java源代码。函数
ButterKnife的注解解析器为ButterknifeProcessor,它的最主要方法以下:oop
//初始化 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); } //在这里定义支持的注解类 @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<String>(); types.add(Bind.class.getCanonicalName()); for (Class<? extends Annotation> listener : LISTENERS) { types.add(listener.getCanonicalName()); } types.add(BindBool.class.getCanonicalName()); types.add(BindColor.class.getCanonicalName()); types.add(BindDimen.class.getCanonicalName()); types.add(BindDrawable.class.getCanonicalName()); types.add(BindInt.class.getCanonicalName()); types.add(BindString.class.getCanonicalName()); return types; } //对注解进行解析 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); try { JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement); Writer writer = jfo.openWriter(); writer.write(bindingClass.brewJava()); writer.flush(); writer.close(); } catch (IOException e) { error(typeElement, "Unable to write view binder for type %s: %s", typeElement, e.getMessage()); } } return true; }
全部经过ButterKnife注解的元素都会走到process方法,在process方法里,首先经过findAndParseTargets(env)方法将全部具备注解的类的信息挨个放在BindingClass里面,最后造成以TypeElement为键,BindingClass为值的键值对。ui
接下来,就只须要循环遍历这个键值对,而后根据TypeElement和BindingClass里面的信息生成对应的java类。例如MainActivity生成的类即为MainActivity$$ViewBinder类。this
下面来看看findAndParseTargets(env)方法里面:rest
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<TypeElement, BindingClass>(); Set<String> erasedTargetNames = new LinkedHashSet<String>(); // Process each @Bind element. for (Element element : env.getElementsAnnotatedWith(Bind.class)) { try { parseBind(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, Bind.class, e); } } // Process each annotation that corresponds to a listener. for (Class<? extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, targetClassMap, erasedTargetNames); } //.... // Try to find a parent binder for each. for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) { String parentClassFqcn = findParentFqcn(entry.getKey(), erasedTargetNames); if (parentClassFqcn != null) { entry.getValue().setParentViewBinder(parentClassFqcn + SUFFIX); } } return targetClassMap; }
这个方法作的主要工做就是将各注解进行分拆遍历,而且进行解析,最后将解析的结果放入targetClassMap,这里重点看Bind注解的解析:code
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<String> erasedTargetNames) { // Verify common generated code restrictions. if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element) || isBindingInWrongPackage(Bind.class, element)) { return; } TypeMirror elementType = element.asType(); if (elementType.getKind() == TypeKind.ARRAY) { parseBindMany(element, targetClassMap, erasedTargetNames); } else if (LIST_TYPE.equals(doubleErasure(elementType))) { parseBindMany(element, targetClassMap, erasedTargetNames); } else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) { error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(), ((TypeElement) element.getEnclosingElement()).getQualifiedName(), element.getSimpleName()); } else { parseBindOne(element, targetClassMap, erasedTargetNames); } }
在这里能够看到ButterKnife会根据值的不一样采起不一样的解析方式,在这里咱们看下最通用的解析方式,即parseBindOne(element,targetClassMap,erasedTargetNames),这也是解析器最核心的地方orm
private void parseBindOne(Element element, Map<TypeElement, BindingClass> 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.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) { error(element, "@%s fields must extend from View or be an interface. (%s.%s)", Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Assemble information on the field. int[] ids = element.getAnnotation(Bind.class).value(); if (ids.length != 1) { error(element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)", Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } if (hasError) { return; } int id = ids[0]; BindingClass bindingClass = targetClassMap.get(enclosingElement); if (bindingClass != null) { ViewBindings viewBindings = bindingClass.getViewBinding(id); if (viewBindings != null) { Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator(); if (iterator.hasNext()) { FieldViewBinding existingBinding = iterator.next(); error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", Bind.class.getSimpleName(), id, existingBinding.getName(), enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } } else { bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement); } String name = element.getSimpleName().toString(); String type = elementType.toString(); boolean required = isRequiredBinding(element); FieldViewBinding binding = new FieldViewBinding(name, type, required); bindingClass.addField(id, binding); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement.toString()); }
在这个方法里面,enclosingElement包含使用Bind注解类的信息,经过下面的方法获取注解标注的值。
int[] ids = element.getAnnotation(Bind.class).value();
接下来的任务就是若是没有建立BindingClass了(若是没有的话),建立BindingClass类也很简单,它的构造函数也只须要包名,类名等基本信息:
private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap, TypeElement enclosingElement) { BindingClass bindingClass = targetClassMap.get(enclosingElement); if (bindingClass == null) { String targetType = enclosingElement.getQualifiedName().toString(); String classPackage = getPackageName(enclosingElement); String className = getClassName(enclosingElement, classPackage) + SUFFIX; bindingClass = new BindingClass(classPackage, className, targetType); targetClassMap.put(enclosingElement, bindingClass); } return bindingClass; }
以后,就将view的信息绑定在FieldViewBinding类的实例中,最后添加到对应的BindingClass实例中,这样使用注解的实例中全部有关注解的信息及这个实体类自己的信息都会在这个BindingClass类的实体类中,ButterKnife接着会利用这个类来生成对应的java文件。
接下来绑定的步骤与上文讲解的相似,基本上都是这个原理,在这里不作赘述,当解析完成以后,全部使用注解的实例的相关信息都会存储在BindingClass的集合内(targetClassMap)。接下来作的工做就是循环遍历这个集合生成文件。
这个方法首先生成包名,而后引入各个类,接下来生成类的主题,经过emitBindMethod(builder)方法生成类的bind(builder)方法,经过emitUnbindMethod(builder)方法生成类的unbind(builder)方法。
private void emitBindMethod(StringBuilder builder) { builder.append(" @Override ") .append("public void bind(final Finder finder, final T target, Object source) {\n"); // Emit a call to the superclass binder, if any. if (parentViewBinder != null) { builder.append(" super.bind(finder, target, source);\n\n"); } if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) { // Local variable in which all views will be temporarily stored. builder.append(" View view;\n"); // Loop over each view bindings and emit it. for (ViewBindings bindings : viewIdMap.values()) { emitViewBindings(builder, bindings); } // Loop over each collection binding and emit it. for (Map.Entry<FieldCollectionViewBinding, int[]> entry : collectionBindings.entrySet()) { emitCollectionBinding(builder, entry.getKey(), entry.getValue()); } } if (!resourceBindings.isEmpty()) { builder.append(" Resources res = finder.getContext(source).getResources();\n"); for (FieldResourceBinding binding : resourceBindings) { builder.append(" target.") .append(binding.getName()) .append(" = res.") .append(binding.getMethod()) .append('(') .append(binding.getId()) .append(");\n"); } } builder.append(" }\n"); } private void emitViewBindings(StringBuilder builder, ViewBindings bindings) { builder.append(" view = "); List<ViewBinding> requiredViewBindings = bindings.getRequiredBindings(); if (requiredViewBindings.isEmpty()) { builder.append("finder.findOptionalView(source, ") .append(bindings.getId()) .append(", null);\n"); } else { if (bindings.getId() == View.NO_ID) { builder.append("target;\n"); } else { builder.append("finder.findRequiredView(source, ") .append(bindings.getId()) .append(", \""); emitHumanDescription(builder, requiredViewBindings); builder.append("\");\n"); } } emitFieldBindings(builder, bindings); emitMethodBindings(builder, bindings); }
能够看到这里先读取每一个绑定view的配置(ViewBindings),接下来绑定view,首先找到activity里面对应的view,而后经过emitFieldBindings(builder,bindings)将成员变量的view注入到activity中,emitMethodBindings(builder.bindings)则用来绑定事件。这里会在下一篇文章详细解说。
当这一切都结束以后,就能够生成文件了,在编译阶段生成的java文件也会生成class文件,在这里为了方便读者理解,给出一个最基本的MainActivity类和生成的MainActivity&&ViewBindler类的源代码:
到这里,咱们就将ButterKnife编译器生成文件部分的代码分析完毕了,更多的细节还请读者本身阅读ButterKnife源码,若是有什么疏漏,还望指出,下一次咱们来分析ButterKnife与上下文绑定。