美文网首页
butterknife

butterknife

作者: Avalon1 | 来源:发表于2017-09-19 11:44 被阅读27次

    首先从使用的地方来看:

    @BindView(R.id.title) TextView title;
    @BindView(R.id.subtitle) TextView subtitle;
    @BindView(R.id.hello) Button hello;
    @BindView(R.id.list_of_things) ListView listOfThings;
    @BindView(R.id.footer) TextView footer;
    @BindString(R.string.app_name) String butterKnife;
    @BindString(R.string.field_method) String fieldMethod;
    @BindString(R.string.by_jake_wharton) String byJakeWharton;
    @BindString(R.string.say_hello) String sayHello;
    

    上面是注解的,下面是在activity的oncreated方法使用的。注解的暂时先不管他,先看一看这个bind(this),方法做了什么!

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

    可以看到这个sourceView其实就是activity的content布局的DecorView。
    再跟踪到【createBinding】

    findBindingConstructorForClass

    image.png

    这里的这个bindingCtor就是两个参数的构造方法。
    然后是

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

    这里就调用了两个参数的构造方法创建一个SimpleActivity_ViewBinding对象。

     @UiThread
    public SimpleActivity_ViewBinding(final SimpleActivity 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);
    view2131099658 = 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);
    view2131099666 = 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'"));
    
    Context context = source.getContext();
    Resources res = context.getResources();
    target.butterKnife = res.getString(R.string.app_name);
    target.fieldMethod = res.getString(R.string.field_method);
    target.byJakeWharton = res.getString(R.string.by_jake_wharton);
    target.sayHello = res.getString(R.string.say_hello);
    

    }
    这样就完成了绑定。
    这个SimpleActivity_ViewBinding类是通过butterknife-compiler自动生成的。
    这些代码是怎么生成的呢?
    首先我们能用的注解是定义在

    image.png

    对这些注解的处理主要是在compiler包下面的ButterKnifeProcessor。的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, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    
    return false;
    

    }

    这里面JavaFile是javapoet这个开源库里面的工具类,主要为了生成类,待会再看
    在生成之前findAndParseTargets(env)查找和解析注解。

    image.png

    其中的一段代码片段。其他的注解的处理类似都是走parseXXXXX。重点看下最常用的BindView吧。

     // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();
    

    拿到注解传入的id参数。

    builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    

    处理完解析之后,是时候来看一看编译器自动生成java类的代码了。

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

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

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

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

    binding.render(sdk)对view绑定的关键在这里

     CodeBlock render(boolean debuggable) {
    CodeBlock.Builder builder = CodeBlock.builder()
        .add("target.$L = $T.$L(", name, UTILS, kind.factoryName);
    for (int i = 0; i < ids.size(); i++) {
      if (i > 0) {
        builder.add(", ");
      }
      builder.add("\n");
    
      Id id = ids.get(i);
      boolean requiresCast = requiresCast(type);
      if (!debuggable) {
        if (requiresCast) {
          builder.add("($T) ", type);
        }
        builder.add("source.findViewById($L)", id.code);
      } else if (!requiresCast && !required) {
        builder.add("source.findViewById($L)", id.code);
      } else {
        builder.add("$T.find", UTILS);
        builder.add(required ? "RequiredView" : "OptionalView");
        if (requiresCast) {
          builder.add("AsType");
        }
        builder.add("(source, $L, \"field '$L'\"", id.code, name);
        if (requiresCast) {
          TypeName rawType = type;
          if (rawType instanceof ParameterizedTypeName) {
            rawType = ((ParameterizedTypeName) rawType).rawType;
          }
          builder.add(", $T.class", rawType);
        }
        builder.add(")");
      }
    }
    return builder.add(")").build();
    }
    

    间接调用findviewbyid()

     public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }

    相关文章

      网友评论

          本文标题:butterknife

          本文链接:https://www.haomeiwen.com/subject/jifwsxtx.html