ButterKnife源碼剖析

轉載于:[http://blog.csdn.net/chenkai19920410/article/details/51020151]
ButterKnife是Jake Wharton大神寫開源框架。
項目托管地址: https://github.com/JakeWharton/butterknife。  
相信不少人已經非常熟悉他的使用了。網上有很多介紹其使用的文章。本文主要是想介紹一下,ButterKnife的實現原理。在閱讀本文之前,可能需要先對Java注解器Annotation Processor有所了解。   
推薦文章:  
 原版文章  
 翻譯版   
讀完該文相信可以對Java Annotation Processor有了比較深入的了解。   
我們知道spring的注解是使用Java反射機制實現的,當然如果讓我們實現注解的話,可能往往也是想到利用反射來實現。但是我們知道如果通過反射,是在運行時(Runtime)來處理View的綁定等一些列事件的,這樣比較耗費資源,會影響應用的性能。所以ButterKnife利用的是上文中提到的Java Annotation Processor技術,自定義了我們平時常用的一些注解,并注冊相應的注解處理器,最后生成了相應的輔助類。在編譯時直接通過輔助類來完成操作,這樣就不會產生過多的消耗,而出現性能問題。接下來我們就一步步分析BufferKnife具體的實現。

自定義的注解類

ButterKnife自定義了很多我們常用的注解,@Bind,@OnClick等。看源碼

/**
 * Bind a field to the view for the specified ID. The view will automatically be cast to the field
 * type.
 * <pre><code>
 * {@literal @}Bind(R.id.title) TextView title;
 * </code></pre>
 */
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
  /** View ID to which the field will be bound. */
  @IdRes int[] value();
}

通過@Target(FIELD)可以看出,該注解是使用在成員變量上的,同樣的我們在看看@OnClick注解的聲明:

/**
 * Bind a method to an {@link OnClickListener OnClickListener} on the view for each ID specified.
 * <pre><code>
 * {@literal @}OnClick(R.id.example) void onClick() {
 *   Toast.makeText(this, "Clicked!", Toast.LENGTH_SHORT).show();
 * }
 * </code></pre>
 * Any number of parameters from
 * {@link OnClickListener#onClick(android.view.View) onClick} may be used on the
 * method.
 *
 * @see OnClickListener
 */
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
    targetType = "android.view.View",
    setter = "setOnClickListener",
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}

注解對象時方法Method,這和我們平時使用方式是吻合的。還有其他的注解,可以在源碼butterknife-annotations.butterknife包下查看。

ButterknifeProcessor

有了這些注解,那么還必須實現注解處理器,ButterKnife的注解處理器是ButterKnifeProcessor類,如果有讀過推薦的文章就會知道該類是繼承自AbstractProcessor的。這里先簡單的介紹一下AbstractProcessor。
  
自定義的Processor都必須繼承自AbstractProcessor,并重寫process方法,不過我們往往還會重寫其他的方法,如下:

public class TProcessor extends AbstractProcessor{
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }
}

同樣的ButterKnifeProcessor主要也是實現了這幾個方法。我們具體來看看:

private Elements elementUtils;
  private Types typeUtils;
  private Filer filer;

@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;
  }

首先來看init(ProcessingEnvironment env)方法,ProcessingEnviroment參數提供很多有用的工具類Elements, Types和Filer。在解析器工作的時候,會掃描所有的Java源文件。源代碼的每一個部分都是一個特定類型的Element。也就是說Element代表程序的元素,例如包、類或者方法。而Types是用來TypeMirror的工具類,Filer用來創建生成輔助文件,這個在后邊會詳細的說明。
  
getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注冊給哪些注解的。它的返回值是一個字符串的集合,包含本處理器想要處理的注解類型的合法全稱。源碼中可以看出ButterknifeProcessor可以處理Bind.class,BindArray.class等一系列注解。

核心注解

getSupportedSourceVersion()用來指定你使用的Java版本。通常這里返回SourceVersion.latestSupported()。Butterknife也是如此。process()方法是ButterknifeProcessor的核心方法。主要是掃描、評估和處理注解,以及生成Java文件。輸入參數RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素。 
    
process()首先通過第36行findAndParseTargets(env)方法掃描所有具有注解的類,并根據這些類的信息生成BindingClass,最后形成以TypeElement為鍵,BindingClass為值的鍵值對。接著循環遍歷這個鍵值對,根據TypeElement和BindingClass里面的信息生成對應的java類。例如AnnotationActivity生成的類即為AnnotationActivity$$ViewBinder類。這就是前邊所說的輔助類,在后邊會詳細介紹,并給出源代碼。現在我們來分析一下findAndParseTargets(env)方法:

 private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    // 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);
      }
    }

    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }
    ...省略...
    // Try to find a parent binder for each.
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
      if (parentType != null) {
        String parentClassFqcn = getFqcn(parentType);
        BindingClass bindingClass = entry.getValue();
        bindingClass.setParentViewBinder(parentClassFqcn + BINDING_CLASS_SUFFIX);
        // Check if parent requested an unbinder.
        BindingClass parentBindingClass = targetClassMap.get(parentType);
        if (parentBindingClass.hasUnbinder()) {
          // Even if the child doesn't request an unbinder explicitly, we need to generate one.
          if (!bindingClass.hasUnbinder()) {
            bindingClass.requiresUnbinder(null);
          }
          // Check if the parent has a parent unbinder.
          if (parentBindingClass.getParentUnbinder() != null) {
            bindingClass.setParentUnbinder(parentBindingClass.getParentUnbinder());
          } else {
            bindingClass.setParentUnbinder(parentClassFqcn + BINDING_CLASS_SUFFIX + "."
                + UnbinderBinding.UNBINDER_SIMPLE_NAME);
          }
        }
      }
    }

    return targetClassMap;
  }

因為該方法實在是太長了,就把類似的代碼省略了,省略的部分是將各注解進行分拆遍歷,并且進行解析,最后將解析的結果放入targetClassMap,原理和解析Bind注解是一樣的,這里我們只用分析一個就可以了。對應的代碼是6~13行。先看第6行,通過遍歷找到所有使用時Bind注解的Element。接著在第9行將得到的Element傳遞給parseBind(element, targetClassMap, erasedTargetNames)方法,從方法名就可以看出,Bind的解析工作是在該方法中完成的。看代碼:

private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<TypeElement> 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);
    }
  }

可以看到parseBind會根據elementType的不同,進行不同的處理,我們就分析一下比較簡單也是最通用的第19行的方法parseBindOne(element, targetClassMap, erasedTargetNames),第11,13行的parseBindMany()方法原理差不多。

private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<TypeElement> 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);
  }

該方法是解析的核心方法,首先通過第4行TypeElement enclosingElement = (TypeElement) element.getEnclosingElement()獲取使用注解的類的信息。我們在拿到使用注解的類的信息之后,會先驗證注解的target的類型是否繼承自view。接著第19行int[] ids = element.getAnnotation(Bind.class).value();獲取注解標注的值。接著看32行到47行, 從targetClassMap中獲取BindingClass實例,(BindingClass類是管理使用注解的實例的所有注解的信息以及實例本身的信息。在最后Butterknife會根據BindingClass類生成相應的輔助類。)如果沒有成功,這會新創建一個BindingClass實例,對應于第46行bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement)。進去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;
  }

從上邊的代碼看創建BindingClass還是蠻簡單的,需要注解對象的類型,然后包名,類名。我們來看看第7行String className = getClassName(enclosingElement, classPackage) + BINDING_CLASS_SUFFIX。其中BINDING_CLASS_SUFFIX是一個定義的常量private static final String BINDING_CLASS_SUFFIX = “$$ViewBinder”,上邊介紹process()方法的時候說過,假如我們使AnnotationActivity使用了注解,那么Butterknife會生成一個AnnotationActivity$$ViewBinder的輔助類。對,輔助類的類名就是這樣來的。接著上邊的說在創建BindingClass后,將其放入targetClassMap。  
 
回到parseBindOne()方法的第53,54行,將view的信息綁定在FieldViewBinding類的實例中,最后添加到上邊生成的BindingClass實例中。到這里我們已經完成了基本的解析工作,并將所有使用注解的實例的信息存儲在targetClassMap中。回到process()方法,為了方便我單獨又把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 {
        bindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
                e.getMessage());
      }
    }

    return true;
  }

通過剛剛分析Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env),我們獲得到存儲使用注解的實例信息的tagetClassMap,接下來的工作就是遍歷tagetClassMap來生成相應的輔助類文件,這個工作是由第10行bindingClass.brewJava().writeTo(filer)完成的。我們知道每個類文件中都是有字符串拼接而成的,而brewJava()就是完成這個工作的,他是BindingClass類中的一個方法,主要就是做一些生成類的bind(),unbind(),注入View,和綁定事件,其中的細節有興趣的可以自己看一看。

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")));
    }

    result.addMethod(createBindMethod());

    if (hasUnbinder()) {
      // Create unbinding class.
      result.addType(createUnbinderClass());
      // Now we need to provide child classes to access and override unbinder implementations.
      createUnbinderInternalAccessMethods(result);
    }

    return JavaFile.builder(classPackage, result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

做完這些工作之后,就可以生成java文件了,看writeTo(Filer filer):

/** Writes this to {@code filer}. */
  public void writeTo(Filer filer) throws IOException {
    String fileName = packageName.isEmpty()
        ? typeSpec.name
        : packageName + "." + typeSpec.name;
    List<Element> originatingElements = typeSpec.originatingElements;
    JavaFileObject filerSourceFile = filer.createSourceFile(fileName,
        originatingElements.toArray(new Element[originatingElements.size()]));
    try (Writer writer = filerSourceFile.openWriter()) {
      writeTo(writer);
    } catch (Exception e) {
      try {
        filerSourceFile.delete();
      } catch (Exception ignored) {
      }
      throw e;
    }
  }

生成的java文件在編譯后會生成相應的class文件,下圖是我上文提到的使用注解的AnnotationActivity和生成的輔助類AnnotationActivity$$ViewBinder。看圖:


下邊是兩個文件的源碼,  
AnnotationActivity$$ViewBinder

import android.view.View;
import android.widget.Button;
import butterknife.ButterKnife.Finder;
import butterknife.ButterKnife.ViewBinder;
import butterknife.internal.DebouncingOnClickListener;

public class AnnotationActivity$$ViewBinder<T extends AnnotationActivity>
  implements ButterKnife.ViewBinder<T>
{
  public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
  {
    View localView = (View)paramFinder.findRequiredView(paramObject, 2131558508, "field 'button' and method 'Onclick'");
    paramT.button = ((Button)paramFinder.castView(localView, 2131558508, "field 'button'"));
    localView.setOnClickListener(new DebouncingOnClickListener()
    {
      public void doClick(View paramAnonymousView)
      {
        paramT.Onclick();
      }
    });
  }

  public void unbind(T paramT)
  {
    paramT.button = null;
  }
}

AnnotationActivity

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import butterknife.Bind;
import butterknife.OnClick;

public class AnnotationActivity extends Activity
{

  @Bind({2131558508})
  Button button;

  @OnClick({2131558508})
  public void Onclick()
  {
    Toast.makeText(this, "helloWorld", 0).show();
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130968601);
    ButterKnife.bind(this);
  }

}

從上邊的代碼可以看到AnnotationActivity$$ViewBinder實現了ButterKnife.ViewBinder接口,他的定義是這樣的

package butterknife.internal;

public interface ViewBinder<T> {
  void bind(Finder finder, T target, Object source);
}

這個是很重要的,后邊會說到。接下來我們看他們之間是怎么工作的。我們平時使用的時候都是ButterKnife.bind()方法來綁定上下文的。那么我們就從這開始分析吧。 ButterKnife.bind()有還幾個重載的方法,分別對應不同的實例,就像我們可以綁定Activity,Fragment,Viewholder等,我們以Activity為例

/**
   * Bind annotated fields and methods in the specified {@code target} using the {@code source}
   * {@link Activity} as the view root.
   *
   * @param target Target class for view binding.
   * @param source Activity on which IDs will be looked up.
   */
  public static void bind(@NonNull Object target, @NonNull Activity source) {
    bind(target, source, Finder.ACTIVITY);
  }

這里的Finder是個enum類,有VIEW,ACTIVITY,DIALOG類型,我們這里傳的是ACTIVITY類型。接著看:

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);
    }
  }

第5行findViewBinderForClass(targetClass)獲取與此Activity對應的編譯階段生成java類的實例:

@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");
      //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;
  }

首先會先在BINDERS中獲取對應的實例,BINDERS是一個以Class為鍵,ViewBinder為值得Map。如果沒有獲取到實例那么通過15-22行代碼通過反射生成ViewBinder實例。對你沒有看錯,這里大家會不會有疑問,不是說使用反射機制很慢嗎,為什么ButterKnife還使用,如果你讀過之前推薦的文章應該知道答案。如果沒有注意,也沒有關系,我們自己看代碼也可以得到答案,通過反射確實是會有性能問題,但是通過反射可以自動的生成實例,而不需要開發者手動生成。并且請看上邊代碼第23行BINDERS.put(cls, viewBinder),將通過反射生成的實例,放在了BINDERS中,也就是說,只是第一次使用了反射,后邊直接從BINDERS取就可以了。這很好的解決了一下性能問題。
  
繼續向下,回到bind()方法第6行viewBinder.bind(finder, target, source),調用了ViewBinder接口的bind方法,記得之前說過ViewBinder接口很重要,就是在這里。因為我們生成的AnnotationActivity$$ViewBinder的那個輔助類是實現了ViewBinder的接口了的,所以這里bind方法的具體實現是在AnnotationActivity$$ViewBinder中的bind()中。為了方便,我把之前的代碼貼過來

public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
  {
    View localView = (View)paramFinder.findRequiredView(paramObject, 2131558508, "field 'button' and method 'Onclick'");
    paramT.button = ((Button)paramFinder.castView(localView, 2131558508, "field 'button'"));
    localView.setOnClickListener(new DebouncingOnClickListener()
    {
      public void doClick(View paramAnonymousView)
      {
        paramT.Onclick();
      }
    });
  }

第3行會執行Finder這個enum類的findRequiredView方法,到這兒你就可以徹底明白了真相。

public <T> T findRequiredView(Object source, int id, String who) {
    T view = findOptionalView(source, id, who);
    if (view == null) {
      String name = getResourceEntryName(source, id);
      throw new IllegalStateException("Required view '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
          + " (methods) annotation.");
    }
    return view;
  }

  public <T> T findOptionalView(Object source, int id, String who) {
    View view = findView(source, id);
    return castView(view, id, who);
  }

findView是一個抽象方法,會根據我們之前綁定上下文的類型來執行,我們之前傳的是Finder.ACTIVITY,所以會執行以下代碼:

 ACTIVITY {
      @Override 
      protected View findView(Object source, int id) {
      return ((Activity) source).findViewById(id);
    }

哈哈,終于找到了,findViewById()。大家都懂。好了,我們在回到AnnotationActivity$$ViewBinder的bind()方法看看第4-11行:

paramT.button = ((Button)paramFinder.castView(localView, 2131558508, "field 'button'"));
    localView.setOnClickListener(new DebouncingOnClickListener()
    {
      public void doClick(View paramAnonymousView)
      {
        paramT.Onclick();
      }
    });

第1行這里有一個細節paramT.button,對應于AnnotationActivity就是AnnotationActivity.this.button這里解釋了ButterKnife為什么要求使用注解的成員要使用public和protected,因為如果使用private的話,上邊是直接找不到button的,只能通過反射,這是萬萬不能忍受的。   接著看代碼,喜大淚奔,熟悉的代碼!不過這里還是要說一下這里的DebouningOnClickListener實現了View.OnClickListener的接口。

總結一下

ButterKnife的ButterKnifeProcessor在編譯時會掃描你的Java代碼中所有使用@Bind,@OnClick等注解,如果發現存在注解,那么通過一系列的解析工作,生成一個類似AnnotationActivity$$ViewBinder(className$$ViewBinder)的Java類,這個類實現了ViewBinder接口。這個生成類中實現了各注解對應的代碼像@Bind最終會執行findViewById(),@OnClick最終會執行setOnClickListener()。我們在調用ButterKnife.bind()時自動的加載上下文對應的生成的實例。好了,就到這兒吧,水平有限,如果有什么不對地方,還請各位留言指正。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容