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()方法。具體怎麼生成的能夠查看源代碼,挺簡單的,這裏不分析了。至此框架在編譯期的處理結束了。