butterknife 10.1.0 核心源码分析

久未见 提交于 2019-12-05 01:58:17

项目结构

butterknife-runtime 
butterknife 
butterknife-annotations
butterknife-compiler 
butterknife-gradle-plugin
//以下是辅助
butterknife-integration-test
butterknife-lint
butterknife-reflect

项目依赖图:

 

如何使用:

1.先在项目根路径 build.gradle 里添加

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

2.在app module build.gradle 里添加

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-plugin

com.jakewharton:butterknife:10.1.0 这里是使用的人调用的butterknife包 =>butterknife & butterknife-annotations & butterknife-runtime

com.jakewharton:butterknife-compiler 这个是注解解析器 => butterknife-compiler

整体流程:

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部分:

    1、用于模型化Java 程序语言结构的模型化api,包括com.sun.mirror包下的mirror api,javax.lang.model包下的element api 及其他辅助工具类。

    2、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里面的所有的类,不作过多的讨论,都是围绕一个目标进行的,如果还想再细点研究,可以把源代码带入项目进行调试。

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!