ButterKnife 源码解读

动机

一开始感觉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中可以在
Dir
可以看到如下内容

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绑定到AcvitivtyFragment上面。绑定实在运行的时候进行的(ViewBinder在编译时生成的)

参考

http://www.jianshu.com/p/95d4f0eb6027
http://www.jianshu.com/p/4c38616af3a5

我的邮箱:1906362072@qq.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值