Android Butterknife 是一款不错的注解框架,和咱们在运行期处理注解的思想不一样,它是在编译期的时候收集注解信息。在系列一中咱们讲解了编译注解的原理,ButterKnife的原理和那个例子差很少。java
理解这个ButterKnife先从应用讲起。先在项目中添加butterKnife.jar,这个能够从github上找到。git
通常在activity声明一个View:github
第一步:就能够这么写框架
@Bind(R.id.iv_title_left)maven
ImageView iv_title_left;ide
这样省去了findViewById方法。ui
第二步:在onCreate()方法中,添加这行代码:ButterKnife.bind(this);记得要在setContentView(R.layout.activity_main);以后写。。通过以上两步配置好了。那么有两个问题,在ButterKnife.bind()干了什么,框架是怎么处理这个注解的。this
那咱们带着问题来看他的源码,spa
一 编译期rest
咱们先找框架的注解(咱们用了Bind类型的注解,就找它的源码):
@Retention(CLASS) @Target(FIELD) public @interface Bind { /** View ID to which the field will be bound. */ int[] value(); }
这个定义就符合咱们在用的Bind注解。
而后咱们找它的注解处理器:
接着看它process()方法:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { 1 Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env); 2 for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); try { 3 bindingClass.brewJava().writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write view binder for type %s: %s", typeElement, e.getMessage()); } } return true;
targetClassMap的key为一个类元素,value为类中的注解元素信息。进入1中的findAndParseTarget()方法,由于方法中代码比较多,只贴一部分,:
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>(); Set<String> erasedTargetNames = new LinkedHashSet<>(); // 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); } // Process each @BindArray element. for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { try { parseResourceArray(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.class, e); } }
根据几个for循环,咱们看到了
env.getElementsAnnotatedWith(Bind.class)
的相似写法,说明是在收集项目中被注解元素的信息。
而后咱们进入paseBind()看具体是怎么解析的:
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); } }
咱们在应用Bind注解的时候,一般只用了一个ID,因此咱们进入paserBindOne():
方法代码比较多,就贴核心代码:
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(); TypeName type = TypeName.get(elementType); 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());
能够看到若是若是类对应的BindClass不存在的话,就建立一个。
FieldViewBinding binding = new FieldViewBinding(name, type, required); bindingClass.addField(id, binding);
能够看到根据注解元素的名字,类型(获取这些值调用的方法的含义,我在例子中解释了),建立了一个注解字段对应的FieldViewBinding,而后添加到bindingClass中。
如今切回到findPaseTargets()方法,其余对不一样注解类型的解析基本和这个上述对注解Bind解析的流程同样。执行完以后就收集到了全部的注解信息,暂存到targetClassMap中。
接着退回到process()方法,3行代码的意思为每个有注解的类调用brewJava()方法,接着调用writeTo(),查看源代码writeTo()发现是其javapoet的代码,而javapoet是一个生成java源文件的框架。(javapoet是butterKnife经过maven管理引用的一个jar包)。而后进入brewJava()方法看一下:
JavaFile brewJava() { 1 TypeSpec.Builder result = TypeSpec.classBuilder(className) 2 .addModifiers(PUBLIC) 3 .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass))); 4 if (parentViewBinder != null) { 5 result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder), 6 TypeVariableName.get("T"))); 7 } else { 8 result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T"))); } 9 result.addMethod(createBindMethod()); 10 result.addMethod(createUnbindMethod()); return JavaFile.builder(classPackage, result.build()) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
看生成了JavaFile对象,先不分析它的源码,看看它生成的文件,文件路径为:
Weather\.apt_generated\com\hsj\weather\ui\activity\MainActivity$$ViewBinder.java,Weather是个人项目名,
文件内容是:
public class MainActivity$$ViewBinder<T extends com.hsj.weather.ui.activity.MainActivity> implements ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 2131361899, "field 'iv_title_left'"); target.iv_title_left = finder.castView(view, 2131361899, "field 'iv_title_left'"); view = finder.findRequiredView(source, 2131361901, "field 'iv_title_right'"); target.iv_title_right = finder.castView(view, 2131361901, "field 'iv_title_right'"); view = finder.findRequiredView(source, 2131361900, "field 'tv_title_center'"); target.tv_title_center = finder.castView(view, 2131361900, "field 'tv_title_center'"); } @Override public void unbind(T target) { target.iv_title_left = null; target.iv_title_right = null; target.tv_title_center = null; } }
再回头看brewJava()方法,1-8行生成了这个源文件的类头,9行对应生成了bind()方法(根createBindMethod()这个方法名字能够猜出来)10行对应生成unBind()方法。具体怎么生成的能够查看源代码,挺简单的,这里不分析了。至此框架在编译期的处理结束了。