butterknife 10.1.0 核心源碼分析

項目結構html

butterknife-runtime 
butterknife 
butterknife-annotations
butterknife-compiler 
butterknife-gradle-plugin
//如下是輔助
butterknife-integration-test
butterknife-lint
butterknife-reflect

項目依賴圖:java

 

如何使用:android

1.先在項目根路徑 build.gradle 裏添加git

classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'

2.在app module build.gradle 裏添加github

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.jakewharton:butterknife:10.1.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
    implementation 'com.google.android:support-v4:r7'
}

butterknife-gradle-plugin 這裏是butterknife插件 對應項目=>butterknife-gradle-pluginapi

com.jakewharton:butterknife:10.1.0 這裏是使用的人調用的butterknife包 =>butterknife & butterknife-annotations & butterknife-runtime緩存

com.jakewharton:butterknife-compiler 這個是註解解析器 => butterknife-compileroracle

總體流程:app

1.先是從使用者經過註釋添加程序進行使用,也就是使用註解。框架

2. 項目在進行構建的時候 經過 annotationProcessor 將對應的註解進行轉換(JavaPoet) 成對應的java ->最終轉成.class 

3.程序在執行的時候經過調用 bk的相關函數,完成任務

source code ->.java ->.class -> .dex

(source code ->.java 代碼編譯期解析註解,而且生成新的 Java 文件,減小手動的代碼輸入) 

 

核心用到的庫:

javapoet:是用來生成.java文件 https://github.com/square/javapoet

auto-service:是google提供的,註解 Processor,對其生成配置信息

APT:Annotation Processing Tool  用於編譯期處理註解的api組件,提供2部分:

    一、用於模型化Java 程序語言結構的模型化api,包括com.sun.mirror包下的mirror api,javax.lang.model包下的element api 及其餘輔助工具類。

    二、javax.annotation.processing包下用於編寫註解處理器的註解處理api。

https://docs.oracle.com/javase/7/docs/technotes/guides/apt/index.html

[java8 使用Pluggable Annotation Processing API 移除了apt]   

按項目 進行源碼拆解:

butterknife 這庫是給應用引用的。

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

ButterKnife.bind(this) 通過一系列的調用,最終調到如下函數

/**
 **bindView使用@code source在指定的@code target中註釋的字段和方法
 *
 * @param target Target class for view binding.
 * @param source View root on which IDs will be looked up.
 */
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
  Class<?> targetClass = target.getClass();
  if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
  Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

  if (constructor == null) {
    return Unbinder.EMPTY;
  }

  //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
  try {
     //實例化:執行它的構造函數 也就是 下文的MainActivity_ViewBinding()
    return constructor.newInstance(target, source);
  } catch (IllegalAccessException e) {
    throw new RuntimeException("Unable to invoke " + constructor, e);
  } catch (InstantiationException e) {
    throw new RuntimeException("Unable to invoke " + constructor, e);
  } catch (InvocationTargetException e) {
    Throwable cause = e.getCause();
    if (cause instanceof RuntimeException) {
      throw (RuntimeException) cause;
    }
    if (cause instanceof Error) {
      throw (Error) cause;
    }
    throw new RuntimeException("Unable to create binding instance.", cause);
  }
}

 

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { 
  //BINDINGS 是綁定的緩存,由於下面.getClassLoader().loadClass 類的反射,創建緩存,會盡可能減小性能損失
  Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
  if (bindingCtor != null || BINDINGS.containsKey(cls)) {
    if (debug) Log.d(TAG, "HIT: Cached in binding map.");
    return bindingCtor;
  }
  String clsName = cls.getName();
  //過濾框架類
  if (clsName.startsWith("android.") || clsName.startsWith("java.")
      || clsName.startsWith("androidx.")) {
    if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
    return null;
  }
  try {
    //加載clsName "_ViewBinding".java 這個是 【butterknife-compiler】註釋編譯器 在項目編譯時生成的。
    //在後面的會說明
    Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
    //noinspection unchecked
    bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
  } catch (ClassNotFoundException e) {
    if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
    bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
  } catch (NoSuchMethodException e) {
    throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
  }
  //存入緩存
  BINDINGS.put(cls, bindingCtor);
  return bindingCtor;
}

生成如下 ***_ViewBinding 

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view7f05001c;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
   ……
  }

  @Override
  @CallSuper
  public void unbind() {
   ……
  }
}

butterknife-compiler 這庫在項目構建的時候調用的。主要就是將 註解 解析=>生成.java文件。

說白了主要是經過調 ButterKnifeProcessor 裏的process 進行回調完成對註解的解析。butterknife主要是重寫如下方法

init(ProcessingEnvironment processingEnvironment) 

裏面提供了Filer等工具類。註解處理器能夠用Filer類建立新文件(源文件、類文件、輔助資源文件)。由此方法建立的源文件和類文件將由管理它們的工具(javac)處理。 

getSupportedSourceVersion() 
支持JDK的版本

public Set getSupportedAnnotationTypes() 

獲取須要處理的註解

 

public boolean process(annotationsannotations, RoundEnvironment roundEnv)

核心方法,在這裏你能夠掃描和處理註解,並生成java文件。

下面主要分析process處理機制

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //1.查找解析綁定元素 這個是以TypeElement (類,接口,Fragment爲key),BindingSet是這個類上綁定集合
  Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    //2.遍歷解析生成java
  for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
    TypeElement typeElement = entry.getKey();
    BindingSet binding = entry.getValue();
    JavaFile javaFile = binding.brewJava(sdk, debuggable);
    try {
      javaFile.writeTo(filer);
    } catch (IOException e) {
      error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
    }
  }
  return false;
}

step1:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
  Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
  Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

  // Process each @BindAnim element. 解析bindAim元素、
  for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
      //校驗有效性
    if (!SuperficialValidation.validateElement(element)) continue;
    try {
      parseResourceAnimation(element, builderMap, erasedTargetNames);
    } catch (Exception e) {
      logParsingError(element, BindAnim.class, e);
    }
  }

  ....省略些吧 基本一個套路

  // Process each @BindView element.
    // env.getElementsAnnotatedWith(BindView.class)獲取全部使用BindView註解的元素
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
    try {
      parseBindView(element, builderMap, erasedTargetNames);
    } catch (Exception e) {
      logParsingError(element, BindView.class, e);
    }
  }

  // Process each @BindViews element.
  for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
    // we don't SuperficialValidation.validateElement(element)
    // so that an unresolved View type can be generated by later processing rounds
    try {
      parseBindViews(element, builderMap, erasedTargetNames);
    } catch (Exception e) {
      logParsingError(element, BindViews.class, e);
    }
  }

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

  // Associate superclass binders with their subclass binders. This is a queue-based tree walk
  // which starts at the roots (superclasses) and walks to the leafs (subclasses).
    // 將builderMap中的數據添加到隊列中

    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
      new ArrayDeque<>(builderMap.entrySet());
  Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
  while (!entries.isEmpty()) {
      // 出隊列
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

    TypeElement type = entry.getKey();
    BindingSet.Builder builder = entry.getValue();
      // 查找當前類元素的父類元素
    TypeElement parentType = findParentType(type, erasedTargetNames);
    if (parentType == null) {
      bindingMap.put(type, builder.build());
    } else {
      BindingSet parentBinding = bindingMap.get(parentType);
      if (parentBinding != null) {
          // 若是找到父類元素,則給當前類元素對應的BindingSet.Builder設置父BindingSet
          builder.setParent(parentBinding);
        bindingMap.put(type, builder.build());
      } else {
        // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
        entries.addLast(entry);
      }
    }
  }
return bindingMap;
}
/**
   * 解析bindview .
   * @param element
   * @param builderMap
   * @param erasedTargetNames
   */
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
    Set<TypeElement> erasedTargetNames) {
    // 首先要注意,此時element是VariableElement類型的,即成員變量
    // enclosingElement是當前元素的父類元素,通常就是咱們使用ButteKnife時定義的View類型成員變量所在的類,能夠理解爲以前例子中的MainActivity
  TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

  // isInaccessibleViaGeneratedCode必須是類、非靜態、非私有
  // 是否在系統相關的資源中使用了ButteKnife註解
  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();
  }
  Name qualifiedName = enclosingElement.getQualifiedName();
  Name simpleName = element.getSimpleName();

    // 判斷當前元素是不是 View 的子類,或者是接口,不是的話拋出異常 
  if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
    if (elementType.getKind() == TypeKind.ERROR) {
      note(element, "@%s field with unresolved type (%s) "
              + "must elsewhere be generated as a View or interface. (%s.%s)",
          BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
    } else {
      error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
          BindView.class.getSimpleName(), qualifiedName, simpleName);
      hasError = true;
    }
  }

  if (hasError) {
    return;
  }

  // 這裏基本就是構建一個資源。
  //@BindView(R.id.test) 也就是test在R.java裏的資源值 
  int id = element.getAnnotation(BindView.class).value();
  BindingSet.Builder builder = builderMap.get(enclosingElement);
  Id resourceId = elementToId(element, BindView.class, id);
  if (builder != null) {
    // 若是當前id已經被綁定,則拋出異常 
    String existingBindingName = builder.findExistingBindingName(resourceId);
    if (existingBindingName != null) {
      error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
          BindView.class.getSimpleName(), id, existingBindingName,
          enclosingElement.getQualifiedName(), element.getSimpleName());
      return;
    }
  } else {
    // 建立一個新的BindingSet.Builder並返回,而且以enclosingElement 爲key添加到builderMap中 
    builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
  }

  String name = simpleName.toString();
  TypeName type = TypeName.get(elementType);
  boolean required = isFieldRequired(element);

  builder.addField(resourceId, new FieldViewBinding(name, type, required));

  // Add the type-erased version to the valid binding targets set.
  erasedTargetNames.add(enclosingElement);
}
private BindingSet.Builder getOrCreateBindingBuilder(
    Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
  BindingSet.Builder builder = builderMap.get(enclosingElement);
  if (builder == null) {
    builder = BindingSet.newBuilder(enclosingElement);
    builderMap.put(enclosingElement, builder);
  }
  return builder;
}
static Builder newBuilder(TypeElement enclosingElement) {
  TypeMirror typeMirror = enclosingElement.asType();
  // 判斷當前父元素的類型
  boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
  boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
  boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

  TypeName targetType = TypeName.get(typeMirror);
  if (targetType instanceof ParameterizedTypeName) {
    targetType = ((ParameterizedTypeName) targetType).rawType;
  }
  // 獲取父類元素的包名
  String packageName = getPackage(enclosingElement).getQualifiedName().toString();
  // 獲取父類元素的名稱
  String className = enclosingElement.getQualifiedName().toString().substring(
      packageName.length() + 1).replace('.', '$');
  // 生成 java類的名稱
  ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

  boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
  return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

開始生成.java ,經過JavaPoet

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //查找解析綁定元素
  Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
  //遍歷解析生成java step2
  for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
    TypeElement typeElement = entry.getKey();
    BindingSet binding = entry.getValue();
    JavaFile javaFile = binding.brewJava(sdk, debuggable);
    try {
      javaFile.writeTo(filer);
    } catch (IOException e) {
      error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
    }
  }

  return false;
}

step2: 根據對應依賴邏輯生成.java

JavaFile brewJava(int sdk, boolean debuggable) {
  //TypeSpec 表示類、接口、或者枚舉聲明
  TypeSpec bindingConfiguration = createType(sdk, debuggable);
  return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
      .addFileComment("Generated code from Butter Knife. Do not modify!")
      .build();
}
//這裏就是生成 類的一些方法,變量,函數 。
private TypeSpec createType(int sdk, boolean debuggable) {
  TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
      .addModifiers(PUBLIC);
  if (isFinal) {
    result.addModifiers(FINAL);
  }

  if (parentBinding != null) {
    result.superclass(parentBinding.bindingClassName);
  } else {
    //實現unbind
    result.addSuperinterface(UNBINDER);
  }

  if (hasTargetField()) {
    result.addField(targetTypeName, "target", PRIVATE);
  }

  if (isView) { //若是是ivew
    result.addMethod(createBindingConstructorForView());
  } else if (isActivity) {//若是是activity
    result.addMethod(createBindingConstructorForActivity());
  } else if (isDialog) {//若是是dialog 
    result.addMethod(createBindingConstructorForDialog());
  }
  if (!constructorNeedsView()) {
    // Add a delegating constructor with a target type + view signature for reflective use.
    result.addMethod(createBindingViewDelegateConstructor());
  }
  result.addMethod(createBindingConstructor(sdk, debuggable));

  if (hasViewBindings() || parentBinding == null) {
    result.addMethod(createBindingUnbindMethod(result));
  }

  return result.build();
}

小結:

ButterKnife 整個過程是在項目編譯階段完成的,主要用到了 annotationProcessor 和 JavaPoet 技術,使用時經過生成的輔助類完成操做,調用bind(this)裏面 調用了反射,但經過cache緩存,減小性能損耗,不少人都說運行時沒有用到反射、實際上是不對的。

以上分析是主要核心代碼流程,關於Butterknife裏面的全部的類,不做過多的討論,都是圍繞一個目標進行的,若是還想再細點研究,能夠把源代碼帶入項目進行調試。

相關文章
相關標籤/搜索