ButterKnife源码分析

使用

  1. 设置依赖及插件

    1. 在根gradle中,配置ButterKnife插件
    buildscript {
            dependencies {
                ...
                classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
            }
        }
    
    1. 在app gradle中,引用ButterKnife插件及设置ButterKnife依赖
    // 引用`ButterKnife`插件
    apply plugin: 'com.jakewharton.butterknife'
    
    dependencies {
        // 设置`ButterKnife`依赖
        implementation 'com.jakewharton:butterknife:10.1.0'
    	annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
    }
    
  2. 在项目中使用

public class MainActivity extends AppCompatActivity {
	
	// 添加注解
	@BindView(R.id.hello_text)
	TextView helloText;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		//
		ButterKnife.bind(this);
	}
	
	@OnClick(R.id.hello_text)
	public void onViewClicked() {
		Toast.makeText(this, helloText.getText(), Toast.LENGTH_SHORT).show();
	}
}
  1. 运行gradle buildgradle rebuild命令,在app/build/generated/source/apt/debug目录相应的包下会找到生成的类MainActivity_ViewBinding
public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view7f070040;

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

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    view = Utils.findRequiredView(source, R.id.hello_text, "field 'helloText' and method 'onViewClicked'");
    target.helloText = Utils.castView(view, R.id.hello_text, "field 'helloText'", TextView.class);
    view7f070040 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onViewClicked();
      }
    });
  }

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

    target.helloText = null;

    view7f070040.setOnClickListener(null);
    view7f070040 = null;
  }
}

ButterKnife的执行流程大体如下:

  1. 在编译时,通过注解处理器(AnnatitionProcessors)扫描并处理声明的注解,生成相应的Java文件。
    1. 定义扫注解
    2. 定义AbstractProcessor的实现类并重写相应的方法
    3. 注册注解处理器
  2. 在Activity、Fragment或者其他,通过调用ButterKnife.bind(this)方法,查找相应的生成的xxx_ViewBinding并执行,完成findViewBingdingsetOnClick等操作。

源码解析

1. 定义注解

在这里插入图片描述

butter-knife-10.1.0jar包中,声明了一系列用于注解处理器注册处理的注解,比如@BindView@BindViews@onClick等等。以@BindView为例,其声明如下:

@Retention(RUNTIME)
@Target(FIELD)
public @interface BindView {
  @IdRes int value();
}

其中:

  • @Retention(RUNTIME):表明注解只保留在源文件,当.java文件编译成.class文件时,注解被遗弃。
  • @Target(FIELD):表明注解只能用于注解字段
  • @IdRes int value():表明注解的属性为int类型,而且其值为资源引用id,比如android.R.id.hello_text

2. 定义AbstractProcessor的实现类

在自定义注解的处理工具一般都是继承自AbstractProcessor

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

  ***
    
  // 用来处理TypeMirror的工具类.
  private Types typeUtils;
  // 创建文件的工具类
  private Filer filer;


  /**
   * 初始化方法
   * ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。
   */
  @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

     ***
  }

  /**
   * 处理器识别的选项
   */
  @Override public Set<String> getSupportedOptions() {
    return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
  }
  
  /**
   * 用来指定使用的Java版本
   */
  @Override
  public SourceVersion getSupportedSourceVersion() { 
      return SourceVersion.latestSupported();
  }

  /**
  * 注解处理器是注册给哪个注解的
  */
  @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
	//返回支持注解的类型
    return types;
  }


  /**
   * 扫描、评估和处理注解的代码,以及生成Java文件。
   */ 
  @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
	

    // 获取所有注册的注解信息,TypeElement 作为 key,BindingSet 作为 value
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

	// 遍历 map 里面的所有信息,并生成 java 代码
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

	  // 生成 javaFile 对象
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
		//  生成 java 模板代码        
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
	
    return false;
  }
    
  ***
}
  • init(ProcessingEnvironment env):注解处理器的初始化方法,它会被注解处理工具调用,并传入ProcessingEnviroment参数,而ProcessingEnviroment会提供很多有用的工具类,比如Elements, TypesFiler等等。
  • getSupportedSourceVersion():用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()
  • getSupportedAnnotationTypes():用来声明注解处理器注册处理哪些注解。其返回值是一个字符串的集合,字符串元素为本处理器想要处理的注解类型的合法全称。
  • process(Set<? extends TypeElement> annotations, RoundEnvironment env): 注解处理器的核心方法,主要负责扫描、评估和处理注解的代码,以及生成Java文件。传入的参数RoundEnviroment可以让查询出包含特定注解的被注解元素。

不管是init(ProcessingEnvironment env)还是getSupportedSourceVersion(),都是一些常规的处理模式。在ButterKnifeProcessor中,值得关注的是注册处理哪些注解和怎么处理注解并生成Java文件的。

ButterKnifeProcessor中,重写getSupportedSourceVersion()方法,注册了一系列支持处理的注解,详见getSupportedAnnotations()法。


/**
* 注解处理器是注册给哪个注解的
*/
@Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    //返回支持注解的类型
    return types;
}

/**
* 支持注解的类型
*/ 
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);
    
    return annotations;
}

接下来就是核心部分 - process()方法,它主要完成了以下工作:

  1. 扫描所有注册的注解信息,处理并这些注解注解的元素的信息,将这些信息整合成BindingSet,它实际就是包含构建Java文件的所有元素信息的对象,然后将这些BindingSet缓存到map
  2. 遍历map里面的BindingSet,生成相应的Java文件。
/**
* 扫描、评估和处理注解的代码,以及生成Java文件。
*/ 
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    // 获取所有注册的注解信息,TypeElement 作为 key,BindingSet 作为 value
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    
    // 遍历 map 里面的所有信息,并生成 java 代码
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
    
      // 生成 javaFile 对象
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
    	//  生成 java 模板代码        
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    
    return false;
}

扫描&处理注解

process()中,通过调用findAndParseTargets()方法来处理被注解的元素信息。在findAndParseTargets()方法中,主要做了两件事:

  1. 通过注解处理工具传入的RoundEnvironment对象调用getElementsAnnotatedWith()获取所有被某个注解注解的元素列表,将解析成的BindingSet.Builder的放入map中缓存;
  2. 将超类Binder与其子类Binder关联。

先看findAndParseTargets()方法的前半部分,了解是如何将注解解析成BindingSet对象的。

/**
* 获取所有注册的注解信息,TypeElement(被注解元素所在的top_level类) 作为 key,BindingSet 作为 value
*/
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    
    // 用于保存所有被注解元素所在的top_level类,为超类Binder与其子类Binder关联做准备
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
    // 处理 @BindAnim 注解
    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);
      }
    }
    
    // 处理 @BindArray 注解 
    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceArray(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindArray.class, e);
      }
    }
    
    // 处理 @BindBitmap 注解 
    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBitmap(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBitmap.class, e);
      }
    }
    
    // 处理 @BindBool 注解 
    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBool(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBool.class, e);
      }
    }
    
    // 处理 @BindColor 注解 
    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceColor(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindColor.class, e);
      }
    }
    
    // 处理 @BindDimen 注解 
    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDimen(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDimen.class, e);
      }
    }
    
    // 处理 @BindDrawable 注解
    for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDrawable(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDrawable.class, e);
      }
    }
    
    // 处理 @BindFloat 注解
    for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceFloat(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindFloat.class, e);
      }
    }
    
    // 处理 @BindFont 注解
    for (Element element : env.getElementsAnnotatedWith(BindFont.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceFont(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindFont.class, e);
      }
    }
    
    // 处理 @BindInt 注解
    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
        
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceInt(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindInt.class, e);
      }
    }
    
    // 处理 @BindString 注解
    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceString(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindString.class, e);
      }
    }
    
    // 处理 @BindView 注解
    // 遍历所有被 @BindView注解的元素并处理
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    
    // 处理 @BindViews 注解
    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);
      }
    }
    
    // 处理与监听对应的每个注解.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }
    
    
    ***
}

对于不同的注解,findAndParseTargets()采用了类似的处理方式,目的都是一样的,就是遍历所有被注解的元素,并创建相应BindingSet.Builder实例,再添加到缓存。以@BindView为例,对于单个被注解的元素的处理是调用parseBindView(element, builderMap, erasedTargetNames)方法,传递3个参数:

  • element:待处理的元素
  • builderMap:缓存BindingSet.Builder的Map
  • erasedTargetNames:缓存所有被注解的元素所属的TypeElement。该Set在处理超类Binder与其子类Binder关联时使用。

parseBindView()中,做了如下操作:

  1. 获取元素所属的TypeElement,以下称enclosingElement
  2. 判断被注解@BindView修饰的成员变量的合法性
    1. 如果该属性是被 private 或者 static 修饰的,则出错
    2. 如果该属性所在top_lel Class不是 Class类型,比如接口或者枚举类,则出错
    3. 如果该属性所在top_lel Class是被 private 修饰的,则出错
    4. 判断是否被注解在错误的包中,若包名以“android”或者“java”开头,则出错
    5. 判断元素的类型是否为View的子类,如果不是,则报错。
    6. 判断元素是不是View及其子类或者是不是Interface
  3. 获取View的id,即@BindView的属性值
  4. 创建或设置BindingSet.Builder
    1. 验证缓存map中key为enclosingElementBindingSet.Builder是否存在,如果存在,使用缓存中的BindingSet.Builder
      • 需要验证当前id下是否已经绑定,如果已绑定,则报错。
    2. 如果不存在,那么需要新创建BindingSet.Builder,并添加到缓存
  5. 获取被注解修饰的字段的信息:字段的名称、类型以及是否需要初始化
  6. 将字段绑定信息(FieldViewBinding)添加到BindingSet.Builder
  7. 将元素所属的TypeElement添加到缓存erasedTargetNames
/**
* 处理 @BindView 并将元素信息添加至对应的BindingSet.Builder
*/
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
  Set<TypeElement> erasedTargetNames) {
    // 获取注解元素所在的元素
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
    // 1. 判断被注解 @BindView 修饰的成员变量是不是合法的
    // 	1.1 如果该属性是被 private 或者 static 修饰的,则出错
    // 	1.2 如果该属性所在top_lel Class不是 Class类型,比如接口或者枚举类,则出错
    // 	1.3 如果该属性所在top_lel Class是被 private 修饰的,则出错
    // 2. 判断是否被注解在错误的包中,若包名以“android”或者“java”开头,则出错
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);
    
    // 判断元素的类型是否为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及其子类或者是不是Interface
    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;
    }
    
    // 获取注解的value值,即为 View的id(R.id.Xxxx)
    int id = element.getAnnotation(BindView.class).value();
    // 根据所在的类元素去查找 builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    Id resourceId = elementToId(element, BindView.class, id);
    // 如果相应的 builder 已经存在
    if (builder != null) {
      // 验证 ID 是否已经被绑定	
      // 如果未绑定,返回null
      // 如果已绑定,返回绑定的字段名称
      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 {
      // 如果没有相应的 builder,就需要新生成,并别存放到  builderMap 中
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
    
    // 字段的名称
    String name = simpleName.toString();
    // 字段的类型
    TypeName type = TypeName.get(elementType);
    // 判断该字段是否一定被初始化
    boolean required = isFieldRequired(element);
    
    // 将该字段添加到BindingSet.Builder
    builder.addField(resourceId, new FieldViewBinding(name, type, required));
    
    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
}

现在来看,findAndParseTargets()方法的后半部分,它做的唯一一件事就是遍历前面部分用来缓存BindingSet.Builder的Map,有两个目的:

  1. 超类Binder与其子类Binder关联
  2. 创建BindingSet并添加至缓存Map<TypeElement, BindingSet>
/**
* 获取所有注册的注解信息,TypeElement(被注解元素所在的top_level类) 作为 key,BindingSet 作为 value
*/
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    
    ***
    
    // 将超类Binder与其子类Binder关联。 这是一个基于队列的树遍历,它从根(超类)开始,然后遍历叶子(子类)。
    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();
    
      // 获取 type 的父类的 TypeElement
      TypeElement parentType = findParentType(type, erasedTargetNames);
      // 为空,存进 map
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
    	// 获取 parentType 的 BindingSet
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          builder.setParent(parentBinding);
          bindingMap.put(type, builder.build());
        } else {
          // 如果父类Binder的BindingSet还未创建,将其加到队列的尾部,等待下一次处理
          entries.addLast(entry);
        }
      }
    }
    
    return bindingMap;
}

生成Java模板代码

从前面的分析了解到,通过调用findAndParseTargets()方法,解析注册的注解信息
并将所有需要创建Java文件的TypeElement添加到缓存 - Map<TypeElement, BindingSet> bindingMap。此时,只需遍历bindingMap,生成Java模板代码的核心代码如下:

// 遍历 map 里面的所有信息,并生成 java 代码
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
  TypeElement typeElement = entry.getKey();
  BindingSet binding = entry.getValue();

  // 生成 javaFile 对象
  JavaFile javaFile = binding.brewJava(sdk, debuggable);
  try {
	//  生成 java 模板代码        
    javaFile.writeTo(filer);
  } catch (IOException e) {
    error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
  }
}

BindingSet实例调用brewJava()方法生成Java模板代码。在brewJava()中,依据BindingSet,调用JavaPoet一系列方法用来:

  1. 创建Java模板文件,添加修饰符及继承关系
  2. 创建构造函数
/**
* 生成Java文件
*/
JavaFile brewJava(int sdk, boolean debuggable) {
    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 {
      result.addSuperinterface(UNBINDER);
    }
    
    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }
    
    // 创建构造函数
    // 如果是 View 或者是 View 的子类的话,添加构造方法
    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) { // 如果是 Activity 或者是 Activity 的子类的话,添加构造方法
      result.addMethod(createBindingConstructorForActivity()); 
    } else if (isDialog) { // 如果是 Dialog 或者是 Dialog 的子类的话,添加构造方法
      result.addMethod(createBindingConstructorForDialog());
    }
    
    //  如果构造方法不需要 View 参数,添加 需要 View 参数的构造方法
    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) {
      //生成unBind方法
      result.addMethod(createBindingUnbindMethod(result));
    }

return result.build();
}

接下来,看创建绑定构造函数createBindingConstructor(sdk, debuggable)

  1. 根据是否有ViewBinding和方法绑定设置构造函数的参数
  2. 判断是否有继承关系,如果有,添加调用父类的构造方法
  3. 遍历viewBindings,调用addViewBinding生成source.findViewById($L)代码
  4. 遍历resourceBindings,通过调用ResourceBinding.render()方法生成根据id获取资源的代码块
/**
* 创建绑定构造函数
*/
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);
    // 如果有方法绑定,比如 @onClick,那么增加一个 targetTypeName 类型  的方法参数 target,并且是 final 类型的
    if (hasMethodBindings()) {
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else { // 如果没有 ,不是 final 类型的
      constructor.addParameter(targetTypeName, "target");
    }
    
    //如果有注解的 View,那么添加 VIEW 类型 source 参数
    if (constructorNeedsView()) {
      constructor.addParameter(VIEW, "source");
    } else {
      //  添加 Context 类型的 context 参数
      constructor.addParameter(CONTEXT, "context");
    }
    
    if (hasUnqualifiedResourceBindings()) {
      // Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
      constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
          .addMember("value", "$S", "ResourceType")
          .build());
    }
    
    // 如果 @OnTouch 绑定 View,添加 @SuppressLint("ClickableViewAccessibility")
    if (hasOnTouchMethodBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
          .addMember("value", "$S", "ClickableViewAccessibility")
          .build());
    }
    
    // 如果 parentBinding 不为空,调用父类 的构造方法
    if (parentBinding != null) {
      if (parentBinding.constructorNeedsView()) {
        constructor.addStatement("super(target, source)");
      } else if (constructorNeedsView()) {
        constructor.addStatement("super(target, source.getContext())");
      } else {
        constructor.addStatement("super(target, context)");
      }
      constructor.addCode("\n");
    }
    
    //  添加成员变量
    if (hasTargetField()) {
      constructor.addStatement("this.target = target");
      constructor.addCode("\n");
    }
    
    if (hasViewBindings()) {
      if (hasViewLocal()) {
        // Local variable in which all views will be temporarily stored.
        constructor.addStatement("$T view", VIEW);
      }
      
      // 遍历  viewBindings,生成  source.findViewById($L) 代码
      for (ViewBinding binding : viewBindings) {
        addViewBinding(constructor, binding, debuggable);
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        constructor.addStatement("$L", binding.render(debuggable));
      }
    
      if (!resourceBindings.isEmpty()) {
        constructor.addCode("\n");
      }
    }
    
    if (!resourceBindings.isEmpty()) {
      if (constructorNeedsView()) {
        constructor.addStatement("$T context = source.getContext()", CONTEXT);
      }
      if (hasResourceBindingsNeedingResource(sdk)) {
        constructor.addStatement("$T res = context.getResources()", RESOURCES);
      }
      for (ResourceBinding binding : resourceBindings) {
        constructor.addStatement("$L", binding.render(sdk));
      }
    }
    
    return constructor.build();
}

addViewBinding中,生成source.findViewById($L)以对绑定的View初始化。由于方法内是用target.的方式引用被注解的字段,因而被注解的字段不能被private修饰。

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
    if (binding.isSingleFieldBinding()) {
      // Optimize the common case where there's a single binding directly to a field.
      FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
      // 生成`findView`代码块
      // 由于这里是用的`target.`,也就是调用了注解的字段,因而被注解的字段不能被`private`修饰
      CodeBlock.Builder builder = CodeBlock.builder()
          .add("target.$L = ", fieldBinding.getName());
    
      boolean requiresCast = requiresCast(fieldBinding.getType());
      if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
        if (requiresCast) {
          builder.add("($T) ", fieldBinding.getType());
        }
        builder.add("source.findViewById($L)", binding.getId().code);
      } else {
        builder.add("$T.find", UTILS);
        builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
        if (requiresCast) {
          builder.add("AsType");
        }
        builder.add("(source, $L", binding.getId().code);
        if (fieldBinding.isRequired() || requiresCast) {
          builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
        }
        if (requiresCast) {
          builder.add(", $T.class", fieldBinding.getRawType());
        }
        builder.add(")");
      }
      result.addStatement("$L", builder.build());
      return;
}

3. 绑定注入

在通过编译后,注解处理器(AnnatitionProcessors)扫描并处理声明的注解,生成相应的Java文件,即xxx_ViewBindg.Java。此时,相应的findView方法已经生成且是构造函数的代码块,是如何注入的呢?ButterKnife.bind()是所熟知的方式。下面看下bind()实现方式:

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}

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

bind()方法里通过调用findBindingConstructorForClass()查找所对应的xxx_ViewBinding的构造函数对象。然后,利用反射来实例化xxx_ViewBinding,以达到注入的目的。

findBindingConstructorForClass()查找对象时,主体思路是这样的:

  1. 读取缓存,若缓存中存在,直接返回。
  2. 判断传入的Class是否合法,如果不合法,直接返回null
  3. 利用类加载器加载生成的class文件,并获取其构造方法。如果可以获取到,直接返回。如果获取不到,会抛出异常。在异常的处理中,再查找当前class文件的父类的_ViewBinding。在返回之前,将结果存进map集合中,做缓存处理。
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    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;
    }
	
	// 如果是 android ,java 原生的文件,不处理
    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 {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
	  // 在原来所在的类查找
      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);
    }
	// 存进 LinkedHashMap 缓存
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
}

到这里,ButterKnife的源码分析结束。其核心就是通过注解处理器生成Java模板,在运行时直接调用,避免了无谓的重复工作,提高开发效率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值