流行框架源码分析(5)-butterknife源码分析(二)

主目录见:Android高级进阶知识(这是总目录索引)
 昨天我们已经分析完butterknife的注解处理器的收集信息部分,如果还没有看的话可以先看[butterknife源码分析(一)],今天我们就要来解析我们的生成代码部分了,相信今天讲解完大家应该能有一个整体的认识,同时因为今天会用到javapoet,如果不熟悉的可以参考这篇[JavaPoet的基本使用]

我们都是java的人

一.目标

 今天的目标就是通过学习自动生成代码的源码,来让我们对自动生成代码有个认识,然后能以后我们自己写的时候能顺手点,所以目标就是:
1.对javapoet自动生成代码有个认识;
2.对butterknife自动生成代码的过程有个了解;
3.自己能编写和生成自己想要的代码。

二.源码分析

昨天我们已经讲过一部分代码了,今天呢我们就接我们昨天的开始讲我们首先再看下我们的process方法:

1.生成代码部分

  @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, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

我们看到我们findAndParseTargets()方法已经讲解完成,这一步完成之后我们已经把我们的信息都放进bindingMap了,而且我们的信息都已经在BindingSet.Builder对象里面了。我们现在直接来看BindingSet里面的brewJava()方法吧:

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

我们看到我们最后builder方法传进去的bindingClassName.packageName()返回的就是要生成的源码类的包名,然后createType()方法返回的TypeSpec就是要创建的类。那么我们现在就看具体的createType()方法做了些什么:

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

    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, debuggable));

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

    return result.build();
  }

上面这段代码看感觉很长,其实逻辑是很顺很简单的,我们现在一步一步来讲解,首先我们看到以下部分:

//创建一个public的类名为bindingClassName.simpleName的类
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);
    if (isFinal) {
//判断类是不是final,如果是则在类前面添加final
      result.addModifiers(FINAL);
    }
//如果要生成的类有继承关系,则添加上父类
    if (parentBinding != null) {
      result.superclass(parentBinding.bindingClassName);
    } else {
//没有的话就让类实现Unbinder接口
      result.addSuperinterface(UNBINDER);
    }
//如果类中有绑定变量和方法,则需要添加上target变量
    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }

我们上面代码流程已经都注释了,我们看下最终生成的代码会长什么样:

public class MainActivity_ViewBinding implements Unbinder {
   private MainActivity target;
}

我们看到我们上面方法只能生成这些信息。那么我们继续看代码:

 if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }

我们看到这里代码有三个判断,但是里面添加的方法其实都是构造方法,我们挑一个来看,比如我们是要生成的源码是个Activity,我们就看方法createBindingConstructorForActivity:

private MethodSpec createBindingConstructorForActivity() {
    MethodSpec.Builder builder = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC)
        .addParameter(targetTypeName, "target");
    if (constructorNeedsView()) {
      builder.addStatement("this(target, target.getWindow().getDecorView())");
    } else {
      builder.addStatement("this(target, target)");
    }
    return builder.build();
  }

这段代码很简单,是个生成构造方法的代码,我们来看对应的生成完的代码:

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

是不是很明了,addAnnotation方法就是添加上UiThread注解,然后addModifiers就是添加这个方法为public的,addParameter就是添加上方法参数,最后添加上面一段代码。接着我们来看下面这句代码:

 result.addMethod(createBindingConstructor(sdk, debuggable));

这段代码也是创建的构造函数,但是不同的是这个构造函数里面会初始化很多东西,我们就来看createBindingConstructor方法干了些啥:

 private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
//添加public和注解@UiThread
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);
//有使用注解@OnClick这些,说明要在click事件用target调用方法,所以target要加上final
    if (hasMethodBindings()) {
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      constructor.addParameter(targetTypeName, "target");
    }

//如果有使用@BindView等说明要查找视图view.findViewById,所以要传进去一个view,不然直接传进去上下文即可
    if (constructorNeedsView()) {
      constructor.addParameter(VIEW, "source");
    } else {
      constructor.addParameter(CONTEXT, "context");
    }

//添加SuppressWarnings注解,当我们id直接使用的是int值则需要添加这个来告诉程序,我们这里是一个忽略这个警告
    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()) {
//父类构造函数需要有view的情况下则添加下面代码
        constructor.addStatement("super(target, source)");
      } else if (constructorNeedsView()) {
//如果有父类,然后又需要有绑定控件则添加这个
        constructor.addStatement("super(target, source.getContext())");
      } else {
//否则直接就添加这句
        constructor.addStatement("super(target, context)");
      }
      constructor.addCode("\n");
    }
//有target的情况下则赋值一下target
    if (hasTargetField()) {
      constructor.addStatement("this.target = target");
      constructor.addCode("\n");
    }

    if (hasViewBindings()) {
      if (hasViewLocal()) {
//添加这个View主要是为了暂时存储所有的控件的
        // Local variable in which all views will be temporarily stored.
        constructor.addStatement("$T view", VIEW);
      }
//遍历添加查找控件的代码,这里等会会详细说
      for (ViewBinding binding : viewBindings) {
        addViewBinding(constructor, binding, debuggable);
      }

//如果是@BindingView里面的id有多个则需要走这个例如:@BindViews({ R.id.title, R.id.subtitle, R.id.hello })这个等会也会详细说
      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方法干了什么:

  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.
//这句代码是添加类似 target.title 的句子
      FieldViewBinding fieldBinding = binding.getFieldBinding();
      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 {
//下面这段添加的主要如 Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class)或者没有AsType
        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();
//我们这里都会判断是不是debug模式,如果不是debug模式就直接findViewById了,如果是且id存在则调用
//另外一个例如:Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'")
    if (!debuggable || 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, debuggable);
    addMethodBindings(result, binding, debuggable);
  }

上面的代码如果直接看我们肯定会晕的,我们必须先来看看我们最终生成的有可能是什么:

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

那么我们再看上面的话就简单非常多了,同时代码有两个方法我们没有说明清楚,addFieldBinding和addMethodBindings,我们来看看这两个是干嘛的吧,我们先来看addFieldBinding方法:

  private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
    FieldViewBinding fieldBinding = binding.getFieldBinding();
    if (fieldBinding != null) {
      if (requiresCast(fieldBinding.getType())) {
        if (debuggable) {
          result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)",
              fieldBinding.getName(), UTILS, binding.getId().code,
              asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType());
        } else {
          result.addStatement("target.$L = ($T) view", fieldBinding.getName(),
              fieldBinding.getType());
        }
      } else {
        result.addStatement("target.$L = view", fieldBinding.getName());
      }
    }
  }

这段代码如果对着生成的源码来看其实也是很简单的....不会吧,什么都说很简单!!!不过确实不难呀,javapoet就是为了简化编程写的嘛,我们来看生成的源码(因为我们现在debug模式,所以生成的代码如下):

  target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);

可以看到我们如果需要类型转化的时候有时debug模式的情况下就会生成这段代码,是不是很简单。然后我们看下我们最后一个比较重要的方法addMethodBindings,这个方法会比较复杂,因为这个方法是添加事件监听的,首先我们来复习下方法的注解@OnClick:


OnClick注解

看完这个注解,有个大致的印象,我们就继续我们代码:

private void addMethodBindings(MethodSpec.Builder result, ViewBinding binding,
      boolean debuggable) {
//我们先是获取到ListenerClass为key,以及对应value为Map(key为ListenerMethod,value为对应的绑定方法)的Map集合
    Map<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> classMethodBindings =
        binding.getMethodBindings();
    if (classMethodBindings.isEmpty()) {
      return;
    }

    // We only need to emit the null check if there are zero required bindings.
//是否需要判断null
    boolean needsNullChecked = binding.getRequiredBindings().isEmpty();
    if (needsNullChecked) {
      result.beginControlFlow("if (view != null)");
    }

    // Add the view reference to the binding.
    String fieldName = "viewSource";
    String bindName = "source";
//判断如果id不为空则执行,例如:  view2131427446 = view;
    if (!binding.isBoundToRoot()) {
      fieldName = "view" + binding.getId().value;
      bindName = "view";
    }
    result.addStatement("$L = $N", fieldName, bindName);

    for (Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> e
        : classMethodBindings.entrySet()) {
//遍历map,然后获取出key和value
      ListenerClass listener = e.getKey();
      Map<ListenerMethod, Set<MethodViewBinding>> methodBindings = e.getValue();

//创建一个匿名内部类,父类为listener 中的type例如:DebouncingOnClickListener
TypeSpec.Builder callback = TypeSpec.anonymousClassBuilder("")
          .superclass(ClassName.bestGuess(listener.type()));
//遍历所有的需要重写的方法,例如:
//      @Override
//      public void doClick(View p0) {
//      
//      }
      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);
        }

        boolean hasReturnType = !"void".equals(method.returnType());
        CodeBlock.Builder builder = CodeBlock.builder();
        if (hasReturnType) {
          builder.add("return ");
        }

        if (methodBindings.containsKey(method)) {
//调用我们判定方法:例如 target.sayHello();或者 target.onItemClick(p2);或者return target.sayGetOffMe();
          for (MethodViewBinding methodBinding : methodBindings.get(method)) {
            builder.add("target.$L(", methodBinding.getName());
            List<Parameter> parameters = methodBinding.getParameters();
            String[] listenerParameters = method.parameters();
            for (int i = 0, count = parameters.size(); i < count; i++) {
              if (i > 0) {
                builder.add(", ");
              }

              Parameter parameter = parameters.get(i);
              int listenerPosition = parameter.getListenerPosition();

              if (parameter.requiresCast(listenerParameters[listenerPosition])) {
                if (debuggable) {
                  builder.add("$T.castParam(p$L, $S, $L, $S, $L, $T.class)", UTILS,
                      listenerPosition, method.name(), listenerPosition, methodBinding.getName(), i,
                      parameter.getType());
                } else {
                  builder.add("($T) p$L", parameter.getType(), listenerPosition);
                }
              } else {
                builder.add("p$L", listenerPosition);
              }
            }
            builder.add(");\n");
          }
        } else if (hasReturnType) {
          builder.add("$L;\n", method.defaultReturn());
        }
        callbackMethod.addCode(builder.build());
        callback.addMethod(callbackMethod.build());
      }
//有的监听可能需要删除,就是去掉这个监听,则需要返回这个监听给一个变量
      boolean requiresRemoval = listener.remover().length() != 0;
      String listenerField = null;
      if (requiresRemoval) {
        TypeName listenerClassName = bestGuess(listener.type());
        listenerField = fieldName + ((ClassName) listenerClassName).simpleName();
        result.addStatement("$L = $L", listenerField, callback.build());
      }
//如果类型是view的类型的话,则执行下面例如:
// ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
//      @Override
//    public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
//        target.onItemClick(p2);
//      }
//    });
      if (!VIEW_TYPE.equals(listener.targetType())) {
        result.addStatement("(($T) $N).$L($L)", bestGuess(listener.targetType()), bindName,
            listener.setter(), requiresRemoval ? listenerField : callback.build());
      } else {
        result.addStatement("$N.$L($L)", bindName, listener.setter(),
            requiresRemoval ? listenerField : callback.build());
      }
    }

    if (needsNullChecked) {
//如果上面有判断null的话则这边要加上接受}符号
      result.endControlFlow();
    }
  }

好啦,我们这段代码的注释也写的非常的详细了,我们如果还不知道的话,我们就举出生成代码的几个例子如下:

view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });

((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });

到这里我们构造方法的内容已经讲完,但是ButterKnife后面又增加了unbind这个方法,我们这里也来看看unbind生成的方法createBindingUnbindMethod做了什么呢?

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

    if (hasTargetField()) {
      if (hasFieldBindings()) {
//添加如下: MainActivity target = this.target;
        result.addStatement("$T target = this.target", targetTypeName);
      }
//if (target == null) throw new IllegalStateException("Bindings already cleared.");
      result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
          "Bindings already cleared.");
//接着把target置为空 this.target = null;
      result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
      result.addCode("\n");
//将所有的变量置为空例如target.title = null;
      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);
      }
    }
//这里的方法解绑就是如下:view2131427446.setOnClickListener(null);或者 ((AdapterView<?>) view2131427447).setOnItemClickListener(null);我就不详细说明了
    if (hasMethodBindings()) {
      result.addCode("\n");
      for (ViewBinding binding : viewBindings) {
        addFieldAndUnbindStatement(bindingClass, result, binding);
      }
    }
//如果父类不为空则调用super.unbind()
    if (parentBinding != null) {
      result.addCode("\n");
      result.addStatement("super.unbind()");
    }
    return result.build();
  }

到这里我们已经生成代码的部分都说明完毕了,其实就是利用javapoet来生成,然后判断之前收集的信息部分符不符合条件来生成相应的代码,逻辑还是比较清晰的。

2.代码的调用

上面我们已经生成了我们想要的代码了,现在当然是要使用我们生成的代码呀,不然还有啥用,我们知道我们使用Butterknife的时候,我们会调用bind()方法和unbind()方法,那么我们知道,我们的方法调用肯定是在这两个方法里面,首先我们来看下bind()方法:

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

 @NonNull @UiThread
  public static Unbinder bind(@NonNull View target) {
    return createBinding(target, target);
  }

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

.........等等

这里面的bind方法有好几个,在不同情况下会调用不同的方法,那么我们直接用第一个方法bind(@NonNull Activity target)来看看,我们看到这个方法里面调用了createBinding()方法:

private static Unbinder createBinding(@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);
    }
  }

我们看到这个方法基本上没啥代码,就是得到构造函数然后反射实例化这个对象。那么我们看这个构造方法是怎么得到的,我们跟进findBindingConstructorForClass方法里面:

  @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
//这段代码就这个关键代码,直接反射拿到构造方法
      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;
  }

我们看到这里面的代码很简单,就是得到对应的绑定类中的构造方法,然后返回回去。这样我们就可以利用反射调用这个构造方法了。然后最后我们再调用unbind即可。unbind的代码跟这个类似,我们就不赘述了,已经讲的非常清楚了,我们就在这里介绍这个框架的解析吧。
总结:今天这个代码主要是利用的javapoet来生成源码,需要的信息主要是上一篇收集信息里面的代码,其实也不是非常难,主要是要有耐心看下去,butterknife的代码确实也不少,还有其他的lint优化和R2的生成源码部分如果有需要我们会拿出来说说,祝大家有享受到源码的魅力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 主目录见:Android高级进阶知识(这是总目录索引) 前面我们已经讲完[编译期注解的使用例子]大家应该对这个流程...
    ZJ_Rocky阅读 1,481评论 0 8
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,795评论 25 707
  • 博文出处:ButterKnife源码分析,欢迎大家关注我的博客,谢谢! 0x01 前言 在程序开发的过程中,总会有...
    俞其荣阅读 2,023评论 1 18
  • 没有执笔的日子,虽然很忙碌,但总有丝丝落寞掠过心底,似乎画画已成了生命中不可或缺的事情,我愿意用无数的线条,把一个...
    灵儿_8ef2阅读 188评论 0 2
  • 那时候最想知道的,也许就是我的初恋的初恋是谁? 一 2003年。 高二文理分科,志和娜娜分到了我带的班级。志学习好...
    全民故事计划阅读 651评论 1 13