一开始感觉ButterKnife通过注解来加载控件和设置监听器会在运行过程中,利用反射来执行,这样的话,会不会导致启动的时候会比较卡!! 然后就称此机会来学习ButterKnife的源码。本文基于butterknife-7.0.1版本进行学习。
首先看一个,使用ButterKnife的简单例子:
@Bind(R.id.button)Button mButton; @Override protected voidon Create(Bundlesaved InstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); } @OnClick(R.id.button) void clickButton() { Toast.makeText(MainActivity.this,“HelloWorld!”,Toast.LENGTH_SHORT).show(); }在上面例子中可以看到,
ButterKnife.bind(this);是整个ButterKnife的入口,下面我们进去看看源代码:
public static void bind(Activity target) { bind(target, target, Finder.ACTIVITY); }这里有很多的bind方法:
// 用于Fragment的绑定 public static void bind(Object target, View source) { bind(target, source, Finder.VIEW); }但是所有的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); } }首先我们找到target的类对象,然后调用findViewBinderForClass来寻找其类对象的ViewBinder。那么什么是ViewBinder呢?
对于每一个类,如果其调用了ButterKnife.bind()方法,那么在编译的过程中,JVM就会为其生成一个ViewBinder对象,该ViewBinder里面存储了所有的@OnClick``@Bind等注解过的对象的一些操作(例如某个按钮的点击事件),这个ViewBinder等哈会在说,这里只要知道就好。
然后,我们进如findViewBinderForClass:
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; } // 判断是否为框架类(android 或 java为前缀的包) 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 { // 实例化该类中的ViewBinder(形如MainActivity$$ViewBinder) 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; }上面代码中一些静态变量如下:
static final Map<Class<?>, ViewBinder<Object>> BINDERS = new LinkedHashMap<>(); public static final String SUFFIX = "$$ViewBinder"; public static final String ANDROID_PREFIX = "android."; public static final String JAVA_PREFIX = "java.";可以看到,对于一个类(MainAcitivty),如果其调用了ButterKnife.bind()方法,那么在编译的过程中,JVM就会为其生成一个MainAcitivty$$ViewBinder对象,如下:
public class MainActivity$$ViewBinder<T extends com.spirittalk.rxjavatraining.MainActivity> implements ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 2131492970, "field 'mButton' and method 'clickButton'"); target.mButton = finder.castView(view, 2131492970, "field 'mButton'"); view.setOnClickListener( new butterknife.internal.DebouncingOnClickListener() { @Override public void doClick(android.view.View p0) { target.clickButton(); } }); } @Override public void unbind(T target) { target.mButton = null; } }这样就可以将MainActivity$$ViewBinder里面的方法来绑定到MainActivity里面去,mButton的点击事件,调用target.clickButton(所以clickButton不能为private,不然就不会被调用,因为这样的调用方法不是反射)。
那么ViewBinder是怎么生成的呢?什么时候生成的呢?
这里就需要Java的一种技术:注解处理器,具体的内容可以看这里,在ButterKnife中可以在 可以看到如下内容
butterknife.internal.ButterKnifeProcessor那么我们去ButterKnifeProcessor看一看: 首先看看getSupportedAnnotationTypes,其表示这个注解处理器可以处理那些注解:
@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; }LISTENERS如下:
private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(// OnCheckedChanged.class, // OnClick.class, // OnEditorAction.class, // OnFocusChange.class, // OnItemClick.class, // OnItemLongClick.class, // OnItemSelected.class, // OnLongClick.class, // OnPageChange.class, // OnTextChanged.class, // OnTouch.class // );主要就是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(); // 生成Java文件 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,部分代码
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); } } return targetClassMap; }然后parseBind
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<String> erasedTargetNames) { /* * isInaccessibleViaGeneratedCode()中验证了: * 1、修饰符不能为private或static;2、不能用于非Class类;3、当前类修饰符不能为private * * isBindingInWrongPackage()验证了,注解Class不能位于Android framework package或Java framework package。 */ if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element) || isBindingInWrongPackage(Bind.class, element)) { return; } TypeMirror elementType = element.asType(); if (elementType.getKind() == TypeKind.ARRAY) { parseBindMany(element, targetClassMap, erasedTargetNames); // @Bind({ R.id.consume_checkbox, R.id.expired_checkbox, R.id.latest_push_checkbox}) List<CheckedTextView> checkedTextViews; 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 { // @Bind(R.id.button) parseBindOne(element, targetClassMap, erasedTargetNames); } }在看看parseBindOne
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()); }这里主要看看getOrCreateTargetClass,这里就是真正的构造要生成的Java代码的结构。
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; }其主要的结构如下:
Map<TypeElement , BindingClass> targetClassMap TypeElement -> 对于每一个类 BindingClass -> Map<Integer, ViewBindings> Integer -> 被{@Bind}注解的控件ID ViewBindings -> 每一个控件的内容(FieldViewBinding,MethodViewBinding)好这里面的逻辑关系只需知道就可以,没必要全都弄清楚。
ButterKnife主要就是在编译过程中,根据注解重新生成一个ViewBinder类,在运行过程中,通过反射将ViewBinder实例化,被注解的元素可以调用ViewBinder里面的方法了。这或许就是一种绑定的方法,将ViewBinder绑定到Acvitivty或Fragment上面。绑定实在运行的时候进行的(ViewBinder在编译时生成的)
http://www.jianshu.com/p/95d4f0eb6027 http://www.jianshu.com/p/4c38616af3a5
我的邮箱:1906362072@qq.com
