butterknife框架浅析

本文深入探讨Butterknife的工作原理,从使用示例入手,详细分析其内部机制,包括注解处理过程、Java文件生成及反编译结果,帮助读者理解Butterknife是如何简化UI绑定的。

说道注解,我想大部分android开发的同学都会有所了解。以前我个人也是不喜欢使用注解框架,大多数注解框架都使用到了反射,反射在运行期间要创建代码然后编译,会降低性能。随着现在android设备配置各种提升,大部分手机还是有能力承受这个开销。反而使用注解框架确实能简化代码,提高开发效率。(配合butterknife插件,有如神助!^_^)。言归正传,还是来说说正事。

首先来看一下butterknife的使用

@Bind(R.id.title) TextView title;
@OnClick(R.id.title) void sayHello() {
    Toast.makeText(this, "Hello!", LENGTH_SHORT).show();
  }

@Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);
    ButterKnife.bind(this);
  }

先看一下 ButterKnife.bind(this) 这个方法,最终会调用到bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder)

static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
    Class<?> targetClass = target.getClass();
    try {
      if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
      ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
      viewBinder.bind(finder, target, source);
    } catch (Exception e) {
      throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
    }
  }

这段代码核心是findViewBinderForClass(targetClass)找到类相对应的viewbinder,然后调用他的bind方法。viewBinder是一个接口,再往下走,无法追踪了。(( ⊙ o ⊙ )该怎么办?全局搜索ViewBinder看哪个类实现它了,然并卵。。。)

骚年别急,县看看findViewBinderForClass(targetClass)这个方法

@NonNull
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.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return NOP_VIEW_BINDER;
    }
    try {
      Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
      Log.i("butter knife", viewBindingClass.getSimpleName());
      //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;
  }

核心代码两行:

Class<?> viewBindingClass=Class.forName(clsName+"$$ViewBinder");
viewBinder = (ViewBinder<Object>)viewBindingClass.newInstance();

找到一个叫XXX$$ViewBinder的类,然后newInstance(),缓存到列表中。这里我们会想,我们并没有见过这样的类啊,又一次迷失了/(ㄒoㄒ)/~~。走到这里,如果对java注解问题不太了解的同学,可能就不知道如何走下去了。不太了解的同学推荐先看一篇文章[http://fyales.com/2015/08/21/Annotation/]

butterknife里面就有一个ButterKnifeProcessor类,在编译时处理注解,生成相应的代码或者文件。

//初始化
@Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
 }
//获取注解支持的类型
@Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    types.add(Bind.class.getCanonicalName());
    for (Class<? extends Annotation> listener : LISTENERS) {
      types.add(listener.getCanonicalName());
    }
    types.add(BindArray.class.getCanonicalName());
    types.add(BindBitmap.class.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());
    types.add(Unbinder.class.getCanonicalName());
    return types;
  }
//核心执行过程
@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 {
        bindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }
    return true;
  }

这里面需要时候的是process里面几个方法Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env)
findAndParseTargets里面的代码太长,只贴一小部分看看

// Process each @Bind element.
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
  if (!SuperficialValidation.validateElement(element)) continue;
  try {
      parseBind(element, targetClassMap, erasedTargetNames);
   } catch (Exception e) {
        logParsingError(element, Bind.class, e);
   }
}

主要是区分不同的注解标签,做相应的处理。

private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    // Verify common generated code restrictions.
    if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
        || isBindingInWrongPackage(Bind.class, element)) {
      return;
    }

    TypeMirror elementType = element.asType();
    //如果类型是数组
    if (elementType.getKind() == TypeKind.ARRAY) {
      parseBindMany(element, targetClassMap, erasedTargetNames);
    } else if (LIST_TYPE.equals(doubleErasure(elementType))) {
      //如果类型是列表
      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 {
      parseBindOne(element, targetClassMap, erasedTargetNames);
    }
  }

根据元素类型用不同的解析方式来处理,看一下

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();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(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());
  }

核心代码:

int[] ids = element.getAnnotation(Bind.class).value();

拿到注解元素的值,也就是代码里面@Bind(R.id.title) ,括号里面的值。
然后将对应的信息,跟view进行绑定。最后所有的信息都到了BindingClass 类中

执行的时候 targetClassMap 没有找到对应的值的,就会跳转到getOrCreateTargetClass(targetClassMap, enclosingElement);`

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) + BINDING_CLASS_SUFFIX;

      bindingClass = new BindingClass(classPackage, className, targetType);
      targetClassMap.put(enclosingElement, bindingClass);
    }
    return bindingClass;
  }

这个方法的就是创建我们前面说到的XXX$$ViewBinder类,这里就知道这个类是怎么来的了。

解析完成之后 回到process方法里面调用

bindingClass.brewJava().writeTo(filer)
JavaFile brewJava() {
    TypeSpec.Builder result = TypeSpec.classBuilder(className)
        .addModifiers(PUBLIC)
        .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
    if (parentViewBinder != null) {
     result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
          TypeVariableName.get("T")));
    } else {
     result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
    }
    if (hasUnbinder()) {
      result.addType(createUnbinderClass());
    }
    result.addMethod(createBindMethod());
    return JavaFile.builder(classPackage, result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

将bindingClass里面的信息,生成相应的java文件,同时编译的时候也会生成相应的class文件。这里就是整个java文件生成的过程。
反编译可以看到生成的文件
这里写图片描述

public class SimpleActivity$$ViewBinder
  implements ViewBinder
{
  public void bind(Finder paramFinder, SimpleActivity paramSimpleActivity, Object paramObject)
  {
    e locale = new e(paramSimpleActivity);
    paramSimpleActivity.title = ((TextView)paramFinder.castView((View)paramFinder.findRequiredView(paramObject, 2130968576, "field 'title'"), 2130968576, "field 'title'"));
    paramSimpleActivity.subtitle = ((TextView)paramFinder.castView((View)paramFinder.findRequiredView(paramObject, 2130968577, "field 'subtitle'"), 2130968577, "field 'subtitle'"));
    View localView1 = (View)paramFinder.findRequiredView(paramObject, 2130968578, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    paramSimpleActivity.hello = ((Button)paramFinder.castView(localView1, 2130968578, "field 'hello'"));
    locale.a = localView1;
    localView1.setOnClickListener(new b(this, paramSimpleActivity));
    localView1.setOnLongClickListener(new c(this, paramSimpleActivity));
    View localView2 = (View)paramFinder.findRequiredView(paramObject, 2130968579, "field 'listOfThings' and method 'onItemClick'");
    paramSimpleActivity.listOfThings = ((ListView)paramFinder.castView(localView2, 2130968579, "field 'listOfThings'"));
    locale.b = localView2;
    ((AdapterView)localView2).setOnItemClickListener(new d(this, paramSimpleActivity));
    paramSimpleActivity.footer = ((TextView)paramFinder.castView((View)paramFinder.findRequiredView(paramObject, 2130968580, "field 'footer'"), 2130968580, "field 'footer'"));
    View[] arrayOfView = new View[3];
    arrayOfView[0] = ((View)paramFinder.findRequiredView(paramObject, 2130968576, "field 'headerViews'"));
    arrayOfView[1] = ((View)paramFinder.findRequiredView(paramObject, 2130968577, "field 'headerViews'"));
    arrayOfView[2] = ((View)paramFinder.findRequiredView(paramObject, 2130968578, "field 'headerViews'"));
    paramSimpleActivity.headerViews = Utils.listOf(arrayOfView);
    paramSimpleActivity.unbinder = locale;
  }
}

然后就可以回到开始说到的位置,找不到谁实现了ViewBinder,生成的SimpleActivity$$ViewBinder实现了ViewBinder接口。接下来的代码就好理解了,调用Finder中的findView,findRequiredView,castView之类,最后就是我们经常写到的findViewById。

很少写博客,文笔粗糙,也没有刻意去整理一下思路,就按照看代码的流程说了一下,有问题的地方,还望大家赐教。

PS:提到注解框架,估计大家也知道Dagger,效率确实比butterknife高,因为它大多都是生成的代码,反射用的很少,弄起来稍微麻烦一点。有兴趣的同学也可以看看Dagger

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值