大家在使用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源文件