ButterKnife源码分析

ButterKinfe的工作原理

1.编译时扫描注解,并作相应处理,使用javapoet库生成Java代码
2.调用ButterKnife.bind(this)方法时候将id和上下文绑定在一起
ButterKinfe使用编译时注解的方式完成Android控件和点击事件的绑定,不用重复的去写findViewById和setOnClicListener等代码,使用方法非常简单,看官方文档 http://jakewharton.github.io/butterknife/
关于注解的相关知识可以看这篇文章https://blog.youkuaiyun.com/Icarus_/article/details/103685601
clone项目到本地 https://github.com/JakeWharton/butterknife
用Android studio打开项目查看源码

1、注解定义

先从@BindView()方法开始看

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

可以看出注解可以保留到class文件中,修饰的是成员变量,定义了一个value()方法,表示只用此注解时候可以接收一个int型的参数,使用@IdRes注解表示参数是一个资源id。

2、注解解析

在butterknife-compiler中,定义了注解处理器ButterKnifeProcessor类

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

使用@AutoService(Processor.class)注册注解解析器,没有使用@SupportedSourceVersion声明所支持的jdk版本,@SupportedAnnotationTypes声明该注解处理器想要处理那些注解。而是使用getSupportedSourceVersion方法返回支持的Java版本,getSupportedAnnotationTypes方法返回支持的注解类型。

//返回支持的注解类型
@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 SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
}

跟进去看一下getSupportedAnnotations,指定ButterKnifeProcessor是注册给哪些注解的

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

ButterKnifeProcessor类通过process方法来完成注解的解析
首先通过 findAndParseTargets方法获取所有注解,并保存到map中,map中的key是TypeElement类型,表示被注解的类,value是BindingSet类型,表示待生成的类所需的所有信息(如包名,类名,绑定的控件等)。

Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

之后遍历这个bindingMap,使用BindingSet对象创建一个JavaFile对象,进而生成Java文件。
process代码如下

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //获取所有注解元素并解析,返回map
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
	//遍历集合所有数据
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
	  //根据BindingSet 生成一个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;
}

分析一下findAndParseTargets方法是如何解析注解的,该方法生成一个Map<TypeElement, BindingSet>包含所有带生成的Java文件需要的信息,创建 Map<TypeElement, BindingSet.Builder>解析所有注解完成builderMap 初始化,最后使用builderMap 完成bindingMap的创建并返回,代码如下。

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    //builderMap集合是为了完成Map<TypeElement, BindingSet>集合的构建
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    ......
    //还有好多类似的解析代码,这里就用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);
      }
    }
    ......
    return bindingMap;
}

他其实是针对每一个自定义的注解,比如上面所说的BindView,BindBitmap等做了处理,选取一个跟进去看一下parseBindView方法。
parseBindView方法会对被注解的元素做可访问检查,包名检查,类型检查,被绑定的元素必须继承View类或者一个接口,然后从builderMap集合中获取被注解的元素所在类的对应的BindingSet.Builder对象,如果没有,调用getOrCreateBindingBuilder创建一个对象,再存入集合中。

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    //获取注解@BindView所在类的TypeElement对象
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    // 首先验证常见的生成代码限制。
    //1.isInaccessibleViaGeneratedCode方法,如果注解的View被private或者static修饰,
    //或者不在一个类中,或在private修饰的类中,返回false
    //2.isBindingInWrongPackage方法,注解的类包名以android.或者以java.开头,返回false
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);
    //TypeElement一些父类信息获取不到,要通过他来获取。asType方法用来验证类型是否继承自view
    TypeMirror elementType = element.asType();
    ......
    //判断被注解的元素是否为view的子类或者接口
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      ......
    }
    //获取被@BindView注解的控件所在类对应BinderSet.Builder对象
    int id = element.getAnnotation(BindView.class).value();
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    Id resourceId = elementToId(element, BindView.class, id);
    if (builder != 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 {
      //获取或者创建一个FindViewBindingBuilder对象,为生成代码做准备
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
    ......
    //添加到map集合中
    builder.addField(resourceId, new FieldViewBinding(name, type, required));
}

接着看一下getOrCreateBindingBuilder方法

private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    //从builderMap中获取注解元素所在类对应的BindingSet.Builder对象
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      //没有则创建
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
}

看一下addField方法,里面调用了getOrCreateViewBindings方法,将id和view进行绑定

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

private ViewBinding.Builder getOrCreateViewBindings(Id id) {
  ViewBinding.Builder viewId = viewIdMap.get(id);
  if (viewId == null) {
    viewId = new ViewBinding.Builder(id);
    viewIdMap.put(id, viewId);
  }
  return viewId;
}
3、生成Java文件

BindingSet对象准备好后,就可以使用BindingSet对象生成一个Java文件,在ButterKnifeProcessor的process方法中调用BindingSet对象的brewJava方法来准备一个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();
}

这里是使用了JavaPoet库,主要调用createType方法完成创作

 private TypeSpec createType(int sdk, boolean debuggable) {
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC)
        .addOriginatingElement(enclosingElement);
    if (isFinal) {
      //是否是final,添加修饰符
      result.addModifiers(FINAL);
    }
    //parentBinding把绑定的集合归在一个集合当中
    if (parentBinding != null) {
      //添加一些父类信息
      result.superclass(parentBinding.getBindingClassName());
    } else {
      result.addSuperinterface(UNBINDER);
    }
    //声明私有成员变量
    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }

    //创建带一个参数的构造方法
    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    //如果构造方法不需要view参数,就需要添加一个view参数的构造方法
    if (!constructorNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor());
    }
    //创建带2个参数构造方法,这个方法就能完成把注解换算成findViewById代码
    result.addMethod(createBindingConstructor(sdk, debuggable));
    //创建解绑方法
    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
}

生成的创建带一个参数的构造方法

@UiThread
public ShareDialogActivity_ViewBinding(ShareDialogActivity target) {
  this(target, target.getWindow().getDecorView());
}

对应的createBindingConstructorForActivity方法为

private MethodSpec createBindingConstructorForActivity() {
    MethodSpec.Builder builder = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)//添加注解@UiThread
        .addModifiers(PUBLIC)//添加public修饰符
        .addParameter(targetTypeName, "target");//添加参数target
    if (constructorNeedsView()) {
      builder.addStatement("this(target, target.getWindow().getDecorView())");
    } else {
      builder.addStatement("this(target, target)");
    }
    return builder.build();
}

创建带2个参数的构造方法

@UiThread
  public ShareDialogActivity_ViewBinding(ShareDialogActivity target, View source) {
    this.target = target;
    target.mRecyclerView = Utils.findRequiredViewAsType(source, R.id.share_dialog_recyclerview, "field 'mRecyclerView'", RecyclerView.class);
    target.mBtnClose = Utils.findRequiredViewAsType(source, R.id.btn_close, "field 'mBtnClose'", ImageView.class);
}

对应的createBindingConstructor方法为

private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);

    if (hasMethodBindings()) {
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      //创建构造方法的参数target
      constructor.addParameter(targetTypeName, "target");
    }

    if (constructorNeedsView()) {
      //创建构造方法的参数source
      constructor.addParameter(VIEW, "source");
    } else {
      constructor.addParameter(CONTEXT, "context");
    }

    ......
    //创建语句this.target=target,换行
    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);
      }
      //创建绑定控件代码
      for (ViewBinding binding : viewBindings) {
        //创建语句Utils.findRequiredViewAsType
        addViewBinding(constructor, binding, debuggable);
      }
      ......
    }
    ......
    return constructor.build();
}
4、绑定bind

在butterkinfe模块下有个ButterKinfe类,里面是绑定的方法,比如看bind(Activity target)这个重载方法,根据传入的activity名字,找到对应生成的以_ViewBinding结尾的类,使用类加载器加载,通过反射调用构造方法,最后调用获取到的构造方法完成注解元素的绑定

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
  View sourceView = target.getWindow().getDecorView();
  return bind(target, sourceView);
}
  @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());
    //找到对应生成的以_ViewBinding结尾的类
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    ......
    try {
      //调用构造方法
      return constructor.newInstance(target, source);
    } 
    ......
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值