深入理解ButterKnife源码并掌握原理(三)

来源:互联网 发布:qq游戏网络不稳定 编辑:程序博客网 时间:2024/05/21 23:31

上两篇我们分析完了处理器的process方法的findAndParseTargets方法来获取了一个集合,该集合包含了你使用注解的类的TypeElement和这个类中的注解的实例BindingClass。
我们再看下处理器的核心方法 process方法

 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { //这个方法查找所有的注解信息,并形成BindingClass集合    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);//遍历生存生成java 文件    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {      TypeElement typeElement = entry.getKey();      BindingClass bindingClass = entry.getValue();      JavaFile javaFile = bindingClass.brewJava();      try {        javaFile.writeTo(filer);      } catch (IOException e) {        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());      }    }//表示处理器正确处理了    return true;  }

我们先看一下BindingClass的一些成员变量。这里我们主要是看关键的一些集合

  //这个是BindView 注解在findAndParseTargets 方法解析后 形成ViewBindings实体通过该类的addxx方法添加到viewIdMap集合的对象  private final Map<Id, ViewBindings> viewIdMap = new LinkedHashMap<>();     //这个是BindViews 注解 在findAndParseTargets 方法解析后 形成的.     BindViews用法:     @BindViews({ R2.id.title, R2.id.subtitle, R2.id.hello }) List<View> headerViews;     //key:FieldCollectionViewBinding 为实体,包含了名字,类型等信息,大家可以看下源码,这里就不说了就是一个java-bean ,你可以这么理解。     //value:     就是这些id: R2.id.title, R2.id.subtitle, R2.id.hello   private final Map<FieldCollectionViewBinding, List<Id>> collectionBindings =      new LinkedHashMap<>();      //该集合是BindBitmap注解在findAndParseTargets 注解在findAndParseTargets 方法解析后 形成FieldBitmapBinding实体通过该类的addxx方法添加到bitmapBindings集合的对象  private final List<FieldBitmapBinding> bitmapBindings = new ArrayList<>();  //这个就是BindDrawable注解....  private final List<FieldDrawableBinding> drawableBindings = new ArrayList<>();  //这个就是BindFloat,BindInt,BindString... 这些注解....  private final List<FieldResourceBinding> resourceBindings = new ArrayList<>();

上面的addxxx方法类似下面的,当然还有其它的就不贴出了

void addFieldCollection(List<Id> ids, FieldCollectionViewBinding binding) {    collectionBindings.put(binding, ids);  }  boolean addMethod(      Id id,      ListenerClass listener,      ListenerMethod method,      MethodViewBinding binding) {    ViewBindings viewBindings = getOrCreateViewBindings(id);    if (viewBindings.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {      return false;    }    viewBindings.addMethodBinding(listener, method, binding);    return true;  }  void addResource(FieldResourceBinding binding) {    resourceBindings.add(binding);  }    //......

至此我们算是完成了一个大步走,我们把所有的注解 方法等放倒了一个集合里,生成java文件的时候就可以遍历这些集合了,从而形成有一定规律的java源文件。
形成的文件就是:
这里我们拿官方的demo-SimpleActivity
编译完后最后生成的文件为:SimpleActivity_ViewBinding
离目标越来越近了。哈哈

.java文件生成

生成.java文件你可以用字符串拼接的笨方法,但是我们的jake大神怎么可能那么弱。人家自己又写了一个库。javapoet ,66666
666

    compile 'com.squareup:javapoet:1.7.0'

这个库可以很方便的生成我们想要的java文件。
在findAndParseTargets方法

 //... //遍历生存生成java 文件    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {      TypeElement typeElement = entry.getKey();      BindingClass bindingClass = entry.getValue();      JavaFile javaFile = bindingClass.brewJava();      try {        javaFile.writeTo(filer);      } catch (IOException e) {        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());      }    }

遍历targetClassMap集合,调用每一个类的brewJava()方法,最后返回JavaFile对象,再通过writeTo方法生成java文件。
接下来我们就看下bindingClass.brewJava()方法。

//我们可以看到javapoet这个开源库使用了设计模式-builder模式。// 我们看源码就是看这些模式啦,思想啦,才能提高自己的代码编写能力//bug少了才有时间干别的呀,哈哈 。。。你懂我的  JavaFile brewJava() {  //参数1:包名   //参数2:TypeSpec,这个可以生成class ,interface 等java文件    return JavaFile.builder(bindingClassName.packageName(), createBindingClass())    //注释        .addFileComment("Generated code from Butter Knife. Do not modify!")        .build();  }

主要步骤
1. 生成类名
2. 生成构造函数
3. 生成unbind方法。

  private TypeSpec createBindingClass() {  1. 生成类名  //bindingClassName.simpleName() 就是我们在findAndParseTargets方法形成的类的名字:xxx_ViewBinding名字  xxx也就是使用butterknife的类名    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())    //增加类修饰符public        .addModifiers(PUBLIC); //使用butterknife的类是 final类    TypeName targetType;    if (isFinal) {     //增加类修饰符FINAL      result.addModifiers(FINAL);      targetType = targetTypeName;    } else {    //不是final类,增加泛型参数T      targetType = TypeVariableName.get("T");      result.addTypeVariable(TypeVariableName.get("T", targetTypeName));    }//如果有父类直接继承    if (hasParentBinding()) {result.superclass(ParameterizedTypeName.get(getParentBinding(), targetType));    } else {    //增加实现接口      result.addSuperinterface(UNBINDER);       //增加成员变量      result.addField(targetType, "target", isFinal ? PRIVATE : PROTECTED);    }    //到这里生成的代码如下:    //比如在activity    public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {  protected T target;  }  //比如在adapter的viewholder中public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder {  private SimpleAdapter.ViewHolder target;}2. 生成构造函数    if (!bindNeedsView()) {      // Add a delegating constructor with a target type + view signature for reflective use.      result.addMethod(createBindingViewDelegateConstructor(targetType));    }    result.addMethod(createBindingConstructor(targetType));  3. 生成unbind方法。    if (hasViewBindings() || !hasParentBinding()) {      result.addMethod(createBindingUnbindMethod(result, targetType));    }    return result.build();  }

(一)生成类名

代码里注释的比较详细了

(二)生成构造函数

主要是createBindingConstructor 方法,主要是对成员变量赋值,以及设置监听事件。先看下javapoet提供的几个方法或类:

1.MethodSpec:生成方法的辅助类

这里主要是生成构造函数,当然也可以生成其它的普通方法,构造函数也是方法的一种吗。
通过constructorBuilder构造出一个方法,通过addAnnotation添加注解,通过addModifiers添加修饰符。

  MethodSpec.Builder constructor =   MethodSpec.constructorBuilder()        .addAnnotation(UI_THREAD)        .addModifiers(PUBLIC);

通过如下方法添加参数targetType为参数类型,”target”为参数的变量名

     constructor.addParameter(targetType, "target");

通过如下方法添加代码语句
第一参数是String类型,可以有占位符ST(类型)等
第二个参数Object… args,类型,多参数不固定。
就像你平时使用String.format()方法一样的意思.

   constructor.addStatement("this.target = target");
2.构造函数中变量的赋值

这里主要是对使用了如下的代码进行一个赋值操作。

  @BindView(R2.id.title) TextView title; @OnClick(R2.id.hello) void sayHello() {}

这里我们主要看一下关键方法,因为都是类似的拼接代码字符串。
我们看一下变量的赋值:

  private void addViewBindings(MethodSpec.Builder result, ViewBindings bindings) {    if (bindings.isSingleFieldBinding()) {      // Optimize the common case where there's a single binding directly to a field.      FieldViewBinding fieldBinding = bindings.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)", bindings.getId().code);      } else {        builder.add("$T.find", UTILS);        builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");        if (requiresCast) {          builder.add("AsType");        }        builder.add("(source, $L", bindings.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<ViewBinding> requiredViewBindings = bindings.getRequiredBindings();    if (requiredViewBindings.isEmpty()) {      result.addStatement("view = source.findViewById($L)", bindings.getId().code);    } else if (!bindings.isBoundToRoot()) {      result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,          bindings.getId().code, asHumanDescription(requiredViewBindings));    }    addFieldBindings(result, bindings);    addMethodBindings(result, bindings);  }

主要是调用系统的findViewById 方法,但是你看到了findRequiredViewAsType,findRequiredView方法和castView方法,findRequiredView,findRequiredViewAsType是作者为乐代码的书写方便对findViewById的一层封装,你可以看一下源码,最后都会调用的findRequiredView方法的findViewById方法。

 public static View findRequiredView(View source, @IdRes int id, String who) {    View view = source.findViewById(id);    if (view != null) {      return view;    }    String name = getResourceEntryName(source, id);    throw new IllegalStateException("Required view '"        + name        + "' with ID "        + id        + " for "        + who        + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"        + " (methods) annotation."); }

这个castView是什么方法呢?是Class类的方法,直接转换为指定的类型

   public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {    try {      return cls.cast(view);    } catch (ClassCastException e) {      String name = getResourceEntryName(view, id);      throw new IllegalStateException("View '"          + name          + "' with ID "          + id          + " for "          + who          + " was of the wrong type. See cause for more info.", e);    }  }

说白了都是调用系统的方法。
好了到这里成员变量的赋值算是完了。
注意一点target.title target就是我们的activity或者view ;也验证了为什么是使用了类似BindView注解不能是private修饰符的另一个原因了。

接下来是方法的监听 private void addMethodBindings(MethodSpec.Builder result, ViewBindings bindings) {}方法,李 main 也是通过循环添加方法借助我们上文提到的
MethodSpec.methodBuilder构造器

  for (ListenerMethod method : getListenerMethods(listener)) {        MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name())            .addAnnotation(Override.class)            .addModifiers(PUBLIC)            .returns(bestGuess(method.returnType()));        String[] parameterTypes = method.parameters();        for (int i = 0, count = parameterTypes.length; i < count; i++) {          callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i);          //...        }

感兴趣的可以根据生成的代码来对照这查看,这里就不多说了。
最后生成的如下所示的代码。

  @UiThread  public SimpleActivity_ViewBinding(final T target, View source) {    this.target = target;    View view;    target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);    target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);    view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);    view2130968578 = view;    view.setOnClickListener(new DebouncingOnClickListener() {      @Override      public void doClick(View p0) {        target.sayHello();      }    });    view.setOnLongClickListener(new View.OnLongClickListener() {      @Override      public boolean onLongClick(View p0) {        return target.sayGetOffMe();      }    });    view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");    target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);    view2130968579 = view;    ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {      @Override      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {        target.onItemClick(p2);      }    });    target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);    target.headerViews = Utils.listOf(        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),         Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),         Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));  }

(三)生成unbind方法

createBindingUnbindMethod方法主要是把的成员变量啦,Listener等 置为空,比如setOnClickListener(null)。

    private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass,      TypeName targetType) {    MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")        .addAnnotation(Override.class)        .addModifiers(PUBLIC);    if (!isFinal && !hasParentBinding()) {      result.addAnnotation(CALL_SUPER);    }    boolean rootBindingWithFields = !hasParentBinding() && hasFieldBindings();    if (hasFieldBindings() || rootBindingWithFields) {      result.addStatement("$T target = this.target", targetType);    }    if (!hasParentBinding()) {      String target = rootBindingWithFields ? "target" : "this.target";      result.addStatement("if ($N == null) throw new $T($S)", target, IllegalStateException.class,          "Bindings already cleared.");    } else {      result.addStatement("super.unbind()");    }    if (hasFieldBindings()) {      result.addCode("\n");      for (ViewBindings bindings : viewIdMap.values()) {        if (bindings.getFieldBinding() != null) {          result.addStatement("target.$L = null", bindings.getFieldBinding().getName());        }      }      for (FieldCollectionViewBinding fieldCollectionBinding : collectionBindings.keySet()) {        result.addStatement("target.$L = null", fieldCollectionBinding.getName());      }    }    if (hasMethodBindings()) {      result.addCode("\n");      for (ViewBindings bindings : viewIdMap.values()) {        addFieldAndUnbindStatement(bindingClass, result, bindings);      }    }    if (!hasParentBinding()) {      result.addCode("\n");      result.addStatement("this.target = null");    }    return result.build();  }

主要就是addStatement方法,上文已经说了,该方法的意思就是生成一句代码,
第一参数是String类型,可以有占位符ST(类型)等
第二个参数Object… args,类型,多参数不固定。
就像你平时使用String.format()方法一样的意思.
比较简单,最后生成的方法类似:

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

深入理解ButterKnife源码并掌握原理(四)

0 0
原创粉丝点击