1. 程式人生 > >高效懶人工具ButterKnife原理解析

高效懶人工具ButterKnife原理解析

大家在使用butterknife的時候,是否注意到,需要呼叫類似這樣的程式碼:ButterKnife.bind(this);

是否想到ButterKnife直接在這裡對該類註解進行反射。那樣ButterKnife就跟其他的ioc框架沒有競爭力了。要知道大量的反射是嚴重影響效能的。

我們進入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); } }


從這個可以看出ButterKnife是呼叫了findViewBinderForClass方法獲取到一個ViewBinder類,ViewBinder類是傳進去的this的類名加上"$$ViewBinder”。最後通過呼叫上面圖的viewBinder.bind進行元件的繫結和監聽。
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_PREFIX) || clsName.startsWith(JAVA_PREFIX)) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return NOP_VIEW_BINDER; } try { 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; }


到這裡,就會想到ViewBinder是哪裡來的呢,我們看編譯後的原始碼檔案會發現,咦,見鬼了,突然間多了一堆以後綴名$$ViewBinder原始檔和class檔案,點選該原始檔檢視,還真的有bind方法。
public class MainActivity$$ViewBinder<T extends com.xpg.menjiamatong_android.activity.MainActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
view = finder.findRequiredView(source, 2131492950, "method 'onClick'");
view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(
          android.view.View p0
        ) {
          target.onClick(p0);
}
      });
}

  @Override public void unbind(T target) {
  }
}
其實這個java檔案是在編譯階段生成的。那麼,編譯階段是執行了哪個類呢,我們看下butterknife原始碼butterknife-7.0.1.jar!/META-INF/services/javax.annotation.processing.Processor檔案中表明瞭哪個類是在編譯階段執行的,
butterknife.internal.ButterKnifeProcessor
這個類就是在編譯階段執行的。ButterKnifeProcessor繼承AbstractProcessor。
ButterKnifeProcessor支援解析的註解有以下這幾種:
@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;
}


包含這個註解型別的類都會被放在RoundEnvironment中,回撥在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 {
      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中對註解型別進行解析,生成Map<TypeElement, BindingClass>。
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);
}
  }

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

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

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

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

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

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

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

  // Try to find a parent binder for each.
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
    String parentClassFqcn = findParentFqcn(entry.getKey(), erasedTargetNames);
    if (parentClassFqcn != null) {
      entry.getValue().setParentViewBinder(parentClassFqcn + SUFFIX);
}
  }

  return targetClassMap;
}

最後,解析Map<TypeElement, BindingClass>,生成前面提到的viewHolder原始檔