Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> butterknife源碼詳解

butterknife源碼詳解

編輯:關於Android編程

作為Android開發者,大家肯定都知道大名鼎鼎的butterknife。它大大的提高了開發效率,雖然在很早之前就開始使用它了,但是只知道是通過注解的方式實現的,卻一直沒有仔細的學習下大牛的代碼。最近在學習運行時注解,決定今天來系統的分析下butterknife的實現原理。

如果你之前不了解Annotation,那強烈建議你先看注解使用.

廢多看圖:

image

從圖中可以很直觀的看出它的module結構,以及使用示例代碼。

它的目錄和我們在注解使用這篇文章中介紹的一樣,大體也是分為三個部分:

app : butterknife api : butterknife-annotations compiler : butterknife-compiler

通過示例代碼我們大體能預料到對應的功能實現:

@BindView(R2.id.hello) Button hello;
BindView注解的作用就是通過value指定的值然後去調用findViewById()來找到對應的控件,然後將該控件賦值給使用該注解的變量。

@OnClick(R2.id.hello) void sayHello() {...}
OnClick注解也是通過指定的id來找到對應控件後,然後對其設置onClickListener並調用使用該注解的方法。

最後不要忘了ButterKnife.bind(this);該方法也是後面我們要分析的突破點。

當然Butterknife的功能是非常強大的,我們在這裡只是用這兩個簡單的例子來進行分析說明。

那我們就來查看BindViewOnclik注解的源碼:

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

作用在變量上的編譯時注解。對該注解的值value()使用android.support.annotation中的IdRes注解,來表明該值只能是資源類型的id

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

作用到方法上的編譯時注解。我們發現該注解還使用了ListenerClass注解,當然從上面的聲明中可以很容易看出它的作用。
那我們就繼續簡單的看一下
ListenerClass`注解的實現:

@Retention(RUNTIME) @Target(ANNOTATION_TYPE)
public @interface ListenerClass {
  String targetType();

  /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */
  String setter();

  /**
   * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If
   * empty {@link #setter()} will be used by default.
   */
  String remover() default "";

  /** Fully-qualified class name of the listener type. */
  String type();

  /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */
  Class> callbacks() default NONE.class;

  /**
   * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()}
   * and an error to specify more than one value.
   */
  ListenerMethod[] method() default { };

  /** Default value for {@link #callbacks()}. */
  enum NONE { }
}

作用到注解類型的運行時注解。

有了之前注解使用這篇文章的基礎,我們知道對於編譯時注解肯定是要通過自定義AbstractProcessor來解析的,所以接下來我們要去butterknife-compiler module中找一下對應的類。通過名字我們就能很簡單的找到:

package butterknife.compiler;

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
   ...
}

通過AutoService注解我們很容易看出來Butterknife也使用了Google Auto。當然它肯定也都用了javaopetandroid-apt,這裡我們就不去分析了。
其他的一些方法我們就不繼續看了,我們接下來看一下具體的核心處理方法,也就是ButterKnifeProcessor.process()方法:

@Override public boolean process(Set elements, RoundEnvironment env) {
    // 查找、解析出所有的注解
    Map targetClassMap = findAndParseTargets(env);
    // 將注解後要生成的相關代碼信息保存到BindingClass類中
    for (Map.Entry entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();
      // 輸出生成的類
      for (JavaFile javaFile : bindingClass.brewJava()) {
        try {
          javaFile.writeTo(filer);
        } catch (IOException e) {
          error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
              e.getMessage());
        }
      }
    }

    return true;
  }

process()方法來看,我們需要主要分析兩個部分:

findAndParseTargets():查找、解析所有的注解 bindingClass.brewJava():生成代碼
第一步:findAndParseTargets()

先查看findAndParseTargets()方法的實現,裡面解析的類型比較多,我們就以BindView為例進行說明:

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

    scanForRClasses(env);

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

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

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

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

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

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

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

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

    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // 檢查一下合法性
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        // 進行解析 
        parseBindView(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

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

    // Process each annotation that corresponds to a listener.
    for (Class listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }

    // Try to find a parent binder for each.
    for (Map.Entry entry : targetClassMap.entrySet()) {
      TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
      if (parentType != null) {
        BindingClass bindingClass = entry.getValue();
        BindingClass parentBindingClass = targetClassMap.get(parentType);
        bindingClass.setParent(parentBindingClass);
      }
    }

    return targetClassMap;
  }

繼續看一下parseBindView()方法:

private void parseBindView(Element element, Map targetClassMap,
      Set erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    // 必須是View類型或者接口
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
          BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }
    // 通過注解的value拿到id
    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();

    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass != null) {
      // 之前已經綁定過該id
      ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));
      if (viewBindings != null && viewBindings.getFieldBinding() != null) {
        FieldViewBinding existingBinding = viewBindings.getFieldBinding();
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBinding.getName(),
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      // 沒有綁定過該id的話就去生成代碼
      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添加代碼
    bindingClass.addField(getId(id), binding);

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

終於進入生成代碼的階段了,繼續看一下getOrCreateTargetClass()的實現:

private BindingClass getOrCreateTargetClass(Map targetClassMap,
      TypeElement enclosingElement) {
    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass == null) {
      TypeName targetType = TypeName.get(enclosingElement.asType());
      if (targetType instanceof ParameterizedTypeName) {
        targetType = ((ParameterizedTypeName) targetType).rawType;
      }
      // 得到包名、類名
      String packageName = getPackageName(enclosingElement);
      String className = getClassName(enclosingElement, packageName);
      // 用包名、類名和_ViewBinder等拼接成要生成的類的全名,這裡會有兩個類:$$_ViewBinder和$$_ViewBinding
      ClassName binderClassName = ClassName.get(packageName, className + "_ViewBinder");
      ClassName unbinderClassName = ClassName.get(packageName, className + "_ViewBinding");

      boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
      // 將要生成的類名,$$_ViewBinder和$$_ViewBinding封裝給BindingClass類
      bindingClass = new BindingClass(targetType, binderClassName, unbinderClassName, isFinal);
      targetClassMap.put(enclosingElement, bindingClass);
    }
    return bindingClass;
  }

繼續看一下BindingClass.addField():

void addField(Id id, FieldViewBinding binding) {
    getOrCreateViewBindings(id).setFieldBinding(binding);
  }

繼續看getOrCreateViewBindings()以及setFieldBinding()方法:

private ViewBindings getOrCreateViewBindings(Id id) {
    ViewBindings viewId = viewIdMap.get(id);
    if (viewId == null) {
      viewId = new ViewBindings(id);
      viewIdMap.put(id, viewId);
    }
    return viewId;
  }

然後看ViewBindings.setFieldBinding()方法:

public void setFieldBinding(FieldViewBinding fieldBinding) {
    if (this.fieldBinding != null) {
      throw new AssertionError();
    }
    this.fieldBinding = fieldBinding;
  }

看到這裡就把findAndParseTargets()方法分析完了。大體總結一下就是把一些變量、參數等初始化到了BindingClass類中。
也就是說上面process()方法中的第一步已經分析完了,下面我們來繼續看第二部分.

第二步:bindingClass.brewJava()

繼續查看BindingClass.brewJava()方法的實現:

Collection brewJava() {
    TypeSpec.Builder result = TypeSpec.classBuilder(binderClassName)
        .addModifiers(PUBLIC, FINAL)
        .addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetTypeName));

    result.addMethod(createBindMethod(targetTypeName));

    List files = new ArrayList<>();
    if (isGeneratingUnbinder()) {
      // 生成$$_ViewBinding類
      files.add(JavaFile.builder(unbinderClassName.packageName(), createUnbinderClass())
          .addFileComment("Generated code from Butter Knife. Do not modify!")
          .build()
      );
    } else if (!isFinal) {
      result.addMethod(createBindToTargetMethod());
    }
    // 生成$$_ViewBinder類
    files.add(JavaFile.builder(binderClassName.packageName(), result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build());

    return files;
  }

看到這裡感覺不用再繼續分析了,該方法就是使用javaopet來生成對應$$_ViewBinder.java類。

到這裡我們已經知道在編譯的過程中會去生成一個對應的$$_ViewBinder.java文件,該類實現了ViewBinder接口。它內部會去生成對應findViewByid()以及setOnClickListener()等方法的代碼,它生成了該類後如何去調用呢?我們也沒有發現new $$_ViewBinder的方法。不要忘了上面我們看到的ButterKnife.bind(this);。接下來我們看一下ButterKnife.bind(this);方法的實現:

/**
   * BindView annotated fields and methods in the specified {@link Activity}. The current content
   * view is used as the view root.
   *
   * @param target Target activity for view binding.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    return getViewBinder(target).bind(Finder.ACTIVITY, target, target);
  }

  /**
   * BindView annotated fields and methods in the specified {@link View}. The view and its children
   * are used as the view root.
   *
   * @param target Target view for view binding.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull View target) {
    return getViewBinder(target).bind(Finder.VIEW, target, target);
  }

調用了getViewBinder()bind()方法,繼續看getViewBinder()方法:

static final Map, ViewBinder

到這裡就徹底分析完了ButterKnife.bind(this);的實現,它其實就相當於new了一個$_ViewBinder類的實例。當然這樣用起來是非常方便的,畢竟我們手動的去new類多不合理,雖然他裡面用到了反射會影響一點點性能,但是他通過內存緩存的方式優化了,我感覺這種方式是利大於弊的。

$_ViewBinder類裡面都是什麼內容呢? 我們去看一下該類的代碼,但是它生成的代碼在哪裡呢?
image

開始看一下SimpleActivity_ViewBinder.bind()方法:

public final class SimpleActivity_ViewBinder implements ViewBinder {
  @Override
  public Unbinder bind(Finder finder, SimpleActivity target, Object source) {
    return new SimpleActivity_ViewBinding<>(target, finder, source);
  }
}

接著看SimpleActivity_ViewBinding類:

public class SimpleActivity_ViewBinding implements Unbinder {
  protected T target;

  private View view2130968578;

  private View view2130968579;

  public SimpleActivity_ViewBinding(final T target, Finder finder, Object source) {
    this.target = target;

    View view;
    target.title = finder.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = finder.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = finder.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = finder.castView(view, R.id.hello, "field 'hello'", Button.class);
    view2130968578 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = finder.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = finder.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
    view2130968579 = view;
    ((AdapterView) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    target.footer = finder.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        finder.findRequiredView(source, R.id.title, "field 'headerViews'"), 
        finder.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
        finder.findRequiredView(source, R.id.hello, "field 'headerViews'"));
  }

  @Override
  public void unbind() {
    T target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.title = null;
    target.subtitle = null;
    target.hello = null;
    target.listOfThings = null;
    target.footer = null;
    target.headerViews = null;

    view2130968578.setOnClickListener(null);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;

    this.target = null;
  }
}

可以看到他內部會通過findViewByid()等來找到對應的View,然後將其賦值給target.xxxx,所以這樣就相當於把所有的控件以及事件都給初始化了,以後就可以直接使用了,通過這裡也可以看到我們在使用注解的時候不要把控件或者方法聲明為private的。

總結一下:

ButterKnifeProcessor會生成$$_ViewBinder類並實現了ViewBinder接口。 $$_ViewBinder類中包含了所有對應的代碼,會通過注解去解析到id等,然後通過findViewById()等方法找到對應的控件,並且復制給調用該方法的來中的變量。這樣就等同於我們直接
使用View v = findViewByid(R.id.xx)來進行初始化控件。

上面雖然生成了$$_ViewBinder類,但是如何去調用呢? 就是在調用ButterKnife.bind(this)時執行,該方法會通過反射去實例化對應的$$_ViewBinder類,並且調用該類的bind()方法。

Butterknife除了在Butterknife.bind()方法中使用反射之外,其他注解的處理都是通過編譯時注解使用,所以不會影響效率。

使用Butterknife是不要將變量聲明為private類型,因為$$_ViewBinder類中會去直接調用變量賦值,如果聲明為private將無法賦值。
java
@BindView(R2.id.title) TextView title; 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved