高效懶人工具ButterKnife原理解析
阿新 • • 發佈:2019-02-06
大家在使用butterknife的時候,是否注意到,需要呼叫類似這樣的程式碼:ButterKnife.bind(this);
是否想到ButterKnife直接在這裡對該類註解進行反射。那樣ButterKnife就跟其他的ioc框架沒有競爭力了。要知道大量的反射是嚴重影響效能的。
我們進入bind方法原始碼看看。static void bind(Object target, Object source, Finder finder) { Class<?> targetClass = target.getClass(); try { if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName()); ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass); if (viewBinder != null) { viewBinder.bind(finder, target, source); } } catch (Exception e) { throw new RuntimeException("Unable to bind views for "+ targetClass.getName(), e); } }
從這個可以看出ButterKnife是呼叫了findViewBinderForClass方法獲取到一個ViewBinder類,ViewBinder類是傳進去的this的類名加上"$$ViewBinder”。最後通過呼叫上面圖的viewBinder.bind進行元件的繫結和監聽。
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) throws IllegalAccessException, InstantiationException { ViewBinder<Object> viewBinder = BINDERS.get(cls); if (viewBinder != null) { if (debug) Log.d(TAG, "HIT: Cached in view binder map."); return viewBinder; } String clsName = cls.getName(); if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return NOP_VIEW_BINDER; } try { Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX); //noinspection unchecked viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance(); if (debug) Log.d(TAG, "HIT: Loaded view binder class."); } catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); viewBinder = findViewBinderForClass(cls.getSuperclass()); } BINDERS.put(cls, viewBinder); return viewBinder; }
到這裡,就會想到ViewBinder是哪裡來的呢,我們看編譯後的原始碼檔案會發現,咦,見鬼了,突然間多了一堆以後綴名$$ViewBinder原始檔和class檔案,點選該原始檔檢視,還真的有bind方法。
public class MainActivity$$ViewBinder<T extends com.xpg.menjiamatong_android.activity.MainActivity> implements ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 2131492950, "method 'onClick'"); view.setOnClickListener( new butterknife.internal.DebouncingOnClickListener() { @Override public void doClick( android.view.View p0 ) { target.onClick(p0); } }); } @Override public void unbind(T target) { } }其實這個java檔案是在編譯階段生成的。那麼,編譯階段是執行了哪個類呢,我們看下butterknife原始碼butterknife-7.0.1.jar!/META-INF/services/javax.annotation.processing.Processor檔案中表明瞭哪個類是在編譯階段執行的,
butterknife.internal.ButterKnifeProcessor這個類就是在編譯階段執行的。ButterKnifeProcessor繼承AbstractProcessor。
ButterKnifeProcessor支援解析的註解有以下這幾種:
@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; }
包含這個註解型別的類都會被放在RoundEnvironment中,回撥在process方法裡。
@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; }
進而在findAndParseTargets中對註解型別進行解析,生成Map<TypeElement, BindingClass>。
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); } // Process each @BindBool element. for (Element element : env.getElementsAnnotatedWith(BindBool.class)) { try { parseResourceBool(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBool.class, e); } } // Process each @BindColor element. for (Element element : env.getElementsAnnotatedWith(BindColor.class)) { try { parseResourceColor(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindColor.class, e); } } // Process each @BindDimen element. for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) { try { parseResourceDimen(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDimen.class, e); } } // Process each @BindDrawable element. for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) { try { parseResourceDrawable(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDrawable.class, e); } } // Process each @BindInt element. for (Element element : env.getElementsAnnotatedWith(BindInt.class)) { try { parseResourceInt(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindInt.class, e); } } // Process each @BindString element. for (Element element : env.getElementsAnnotatedWith(BindString.class)) { try { parseResourceString(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindString.class, e); } } // 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; }
最後,解析Map<TypeElement, BindingClass>,生成前面提到的viewHolder原始檔