ButterKnife的使用介绍及原理探究(四)

上几篇文章主要分析了process方法收集信息的功能,接下来看生成代码,先看源码。

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

在探究生成java代码之前,先科普一下,源码使用了一个叫javapoet的库,想了解这个库的使用,请移步至https://github.com/square/javapoet 或者参考JavaPoet的基本使用.  这是理解源码的基础。

遍历生成Java文件

从源码可以看出,遍历targetClassMap集合,调用每一个类的brewJava()方法,最后返回JavaFile对象,再通过writeTo方法生成java文件。 那么接下来看看brewJava()方法。

JavaFile brewJava(int sdk) {
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
}

这里可以看出JavaFile 这个类的设计采用了建造者模式;builder需要传递两个参数,一个包名,一个TypeSpec,可以生成class, interface等java文件。addFileComment 就是添加注释了。主要来看看createType这个方法。

private TypeSpec createType(int sdk) {
    // 1.生成类名,这里的类名就是前面构造的 xxx_ViewBinding
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);//增加类修饰符为public
    if (isFinal) {
      result.addModifiers(FINAL); //是否为final类
    }

    if (parentBinding != null) { //如果有父类就直接继承
      result.superclass(parentBinding.bindingClassName);
    } 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());
    }
    if (!constructorNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor());
    }
    result.addMethod(createBindingConstructor(sdk));

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

    return result.build();
  }

这个方法主要做了以下几个操作:

  1. 生成类名
  2. 生成构造函数
  3. 生成unbind方法
(一)生成类名

这部分代码里已经注释,为了方便理解,给一个生成好的类的例子:

    // 在activity
    public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
        protected T target;
    }
    //在adapter的viewholder中
    public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder {
        private SimpleAdapter.ViewHolder target;
    }
(二) 生成构造函数
   if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      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));
createBindingConstructor 方法,主要是对成员变量赋值,以及设置监听事件。
private MethodSpec createBindingConstructor(int sdk) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);

    if (hasMethodBindings()) {
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      constructor.addParameter(targetTypeName, "target");
    }

    if (constructorNeedsView()) {
      constructor.addParameter(VIEW, "source");
    } else {
      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());
    }

    if (hasOnTouchMethodBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
          .addMember("value", "$S", "ClickableViewAccessibility")
          .build());
    }

    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);
      }
      for (ViewBinding binding : viewBindings) {
        addViewBinding(constructor, binding);
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        constructor.addStatement("$L", binding.render());
      }

      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方法,这个方法主要还是用到了系统的findViewById方法,看源码;
 private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
    if (binding.isSingleFieldBinding()) {
      // Optimize the common case where there's a single binding directly to a field.
      FieldViewBinding fieldBinding = binding.getFieldBinding();
      CodeBlock.Builder builder = CodeBlock.builder()
          .add("target.$L = ", fieldBinding.getName());

      boolean requiresCast = requiresCast(fieldBinding.getType());
      if (!requiresCast && !fieldBinding.isRequired()) {
        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;
    }

    List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
    if (requiredBindings.isEmpty()) {
      result.addStatement("view = source.findViewById($L)", binding.getId().code);
    } else if (!binding.isBoundToRoot()) {
      result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
          binding.getId().code, asHumanDescription(requiredBindings));
    }

    addFieldBinding(result, binding);
    addMethodBindings(result, binding);
  }
在这段代码里会发现findRequiredView,其实这是作者对findViewById做了一层封装。至此,成员变量的赋值完了,值得注意的是 target就是我们的activity或者view ;也说明使用类似BindView注解不能是private修饰符的一个原因。
(三)生成unbind方法

createBindingUnbindMethod方法主要是把成员变量,Listener等置为空。看代码:

private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
    MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
        .addAnnotation(Override.class)
        .addModifiers(PUBLIC);
    if (!isFinal && parentBinding == null) {
      result.addAnnotation(CALL_SUPER);
    }

    if (hasTargetField()) {
      if (hasFieldBindings()) {
        result.addStatement("$T target = this.target", targetTypeName);
      }
      result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
          "Bindings already cleared.");
      result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
      result.addCode("\n");
      for (ViewBinding binding : viewBindings) {
        if (binding.getFieldBinding() != null) {
          result.addStatement("target.$L = null", binding.getFieldBinding().getName());
        }
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        result.addStatement("target.$L = null", binding.name);
      }
    }

    if (hasMethodBindings()) {
      result.addCode("\n");
      for (ViewBinding binding : viewBindings) {
        addFieldAndUnbindStatement(bindingClass, result, binding);
      }
    }

    if (parentBinding != null) {
      result.addCode("\n");
      result.addStatement("super.unbind()");
    }
    return result.build();
  }

比较简单,最后生成的方法类似于以下代码:

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

    target.word = null;
    target.length = null;
    target.position = null;
    this.target = null;
  }
至此,process方法的整个流程都分析完了。接下来看看对外提供的API是怎样的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值