ButterKnife 源码解读

    xiaoxiao2026-04-15  3

    动机

    一开始感觉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

    那么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

    转载请注明原文地址: https://ju.6miu.com/read-1308869.html
    最新回复(0)