ButterKnife源码剖析

作者: 双鱼大猫 | 来源:发表于2016-12-16 16:58 被阅读99次

    转载于:[http://blog.csdn.net/chenkai19920410/article/details/51020151]
    ButterKnife是Jake Wharton大神写开源框架。
    项目托管地址: https://github.com/JakeWharton/butterknife。  
    相信不少人已经非常熟悉他的使用了。网上有很多介绍其使用的文章。本文主要是想介绍一下,ButterKnife的实现原理。在阅读本文之前,可能需要先对Java注解器Annotation Processor有所了解。   
    推荐文章:  
     原版文章  
     翻译版   
    读完该文相信可以对Java Annotation Processor有了比较深入的了解。   
    我们知道spring的注解是使用Java反射机制实现的,当然如果让我们实现注解的话,可能往往也是想到利用反射来实现。但是我们知道如果通过反射,是在运行时(Runtime)来处理View的绑定等一些列事件的,这样比较耗费资源,会影响应用的性能。所以ButterKnife利用的是上文中提到的Java Annotation Processor技术,自定义了我们平时常用的一些注解,并注册相应的注解处理器,最后生成了相应的辅助类。在编译时直接通过辅助类来完成操作,这样就不会产生过多的消耗,而出现性能问题。接下来我们就一步步分析BufferKnife具体的实现。

    自定义的注解类

    ButterKnife自定义了很多我们常用的注解,@Bind,@OnClick等。看源码

    /**
     * Bind a field to the view for the specified ID. The view will automatically be cast to the field
     * type.
     * <pre><code>
     * {@literal @}Bind(R.id.title) TextView title;
     * </code></pre>
     */
    @Retention(CLASS) @Target(FIELD)
    public @interface Bind {
      /** View ID to which the field will be bound. */
      @IdRes int[] value();
    }
    

    通过@Target(FIELD)可以看出,该注解是使用在成员变量上的,同样的我们在看看@OnClick注解的声明:

    /**
     * Bind a method to an {@link OnClickListener OnClickListener} on the view for each ID specified.
     * <pre><code>
     * {@literal @}OnClick(R.id.example) void onClick() {
     *   Toast.makeText(this, "Clicked!", Toast.LENGTH_SHORT).show();
     * }
     * </code></pre>
     * Any number of parameters from
     * {@link OnClickListener#onClick(android.view.View) onClick} may be used on the
     * method.
     *
     * @see OnClickListener
     */
    @Target(METHOD)
    @Retention(CLASS)
    @ListenerClass(
        targetType = "android.view.View",
        setter = "setOnClickListener",
        type = "butterknife.internal.DebouncingOnClickListener",
        method = @ListenerMethod(
            name = "doClick",
            parameters = "android.view.View"
        )
    )
    public @interface OnClick {
      /** View IDs to which the method will be bound. */
      @IdRes int[] value() default { View.NO_ID };
    }
    

    注解对象时方法Method,这和我们平时使用方式是吻合的。还有其他的注解,可以在源码butterknife-annotations.butterknife包下查看。

    ButterknifeProcessor

    有了这些注解,那么还必须实现注解处理器,ButterKnife的注解处理器是ButterKnifeProcessor类,如果有读过推荐的文章就会知道该类是继承自AbstractProcessor的。这里先简单的介绍一下AbstractProcessor。
      
    自定义的Processor都必须继承自AbstractProcessor,并重写process方法,不过我们往往还会重写其他的方法,如下:

    public class TProcessor extends AbstractProcessor{
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            return false;
        }
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return super.getSupportedAnnotationTypes();
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return super.getSupportedSourceVersion();
        }
    }
    

    同样的ButterKnifeProcessor主要也是实现了这几个方法。我们具体来看看:

    private Elements elementUtils;
      private Types typeUtils;
      private Filer filer;
    
    @Override 
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
    
        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
      }
    
     @Override
     public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
    
        types.add(Bind.class.getCanonicalName());
    
        for (Class<? extends Annotation> listener : LISTENERS) {
          types.add(listener.getCanonicalName());
        }
    
        types.add(BindArray.class.getCanonicalName());
        types.add(BindBitmap.class.getCanonicalName());
        types.add(BindBool.class.getCanonicalName());
        types.add(BindColor.class.getCanonicalName());
        types.add(BindDimen.class.getCanonicalName());
        types.add(BindDrawable.class.getCanonicalName());
        types.add(BindInt.class.getCanonicalName());
        types.add(BindString.class.getCanonicalName());
        types.add(Unbinder.class.getCanonicalName());
    
        return types;
      }
    
    @Override 
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    
        for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingClass bindingClass = entry.getValue();
    
          try {
            bindingClass.brewJava().writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
                e.getMessage());
          }
        }
    
        return true;
      }
    

    首先来看init(ProcessingEnvironment env)方法,ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。在解析器工作的时候,会扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。也就是说Element代表程序的元素,例如包、类或者方法。而Types是用来TypeMirror的工具类,Filer用来创建生成辅助文件,这个在后边会详细的说明。
      
    getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注册给哪些注解的。它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。源码中可以看出ButterknifeProcessor可以处理Bind.class,BindArray.class等一系列注解。

    核心注解

    getSupportedSourceVersion()用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。Butterknife也是如此。process()方法是ButterknifeProcessor的核心方法。主要是扫描、评估和处理注解,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。 
        
    process()首先通过第36行findAndParseTargets(env)方法扫描所有具有注解的类,并根据这些类的信息生成BindingClass,最后形成以TypeElement为键,BindingClass为值的键值对。接着循环遍历这个键值对,根据TypeElement和BindingClass里面的信息生成对应的java类。例如AnnotationActivity生成的类即为AnnotationActivity$$ViewBinder类。这就是前边所说的辅助类,在后边会详细介绍,并给出源代码。现在我们来分析一下findAndParseTargets(env)方法:

     private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
        // Process each @Bind element.
        for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
            parseBind(element, targetClassMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, Bind.class, e);
          }
        }
    
        // Process each annotation that corresponds to a listener.
        for (Class<? extends Annotation> listener : LISTENERS) {
          findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
        }
        ...省略...
        // Try to find a parent binder for each.
        for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
          if (parentType != null) {
            String parentClassFqcn = getFqcn(parentType);
            BindingClass bindingClass = entry.getValue();
            bindingClass.setParentViewBinder(parentClassFqcn + BINDING_CLASS_SUFFIX);
            // Check if parent requested an unbinder.
            BindingClass parentBindingClass = targetClassMap.get(parentType);
            if (parentBindingClass.hasUnbinder()) {
              // Even if the child doesn't request an unbinder explicitly, we need to generate one.
              if (!bindingClass.hasUnbinder()) {
                bindingClass.requiresUnbinder(null);
              }
              // Check if the parent has a parent unbinder.
              if (parentBindingClass.getParentUnbinder() != null) {
                bindingClass.setParentUnbinder(parentBindingClass.getParentUnbinder());
              } else {
                bindingClass.setParentUnbinder(parentClassFqcn + BINDING_CLASS_SUFFIX + "."
                    + UnbinderBinding.UNBINDER_SIMPLE_NAME);
              }
            }
          }
        }
    
        return targetClassMap;
      }
    

    因为该方法实在是太长了,就把类似的代码省略了,省略的部分是将各注解进行分拆遍历,并且进行解析,最后将解析的结果放入targetClassMap,原理和解析Bind注解是一样的,这里我们只用分析一个就可以了。对应的代码是6~13行。先看第6行,通过遍历找到所有使用时Bind注解的Element。接着在第9行将得到的Element传递给parseBind(element, targetClassMap, erasedTargetNames)方法,从方法名就可以看出,Bind的解析工作是在该方法中完成的。看代码:

    private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
          Set<TypeElement> erasedTargetNames) {
        // Verify common generated code restrictions.
        if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
            || isBindingInWrongPackage(Bind.class, element)) {
          return;
        }
    
        TypeMirror elementType = element.asType();
        if (elementType.getKind() == TypeKind.ARRAY) {
          parseBindMany(element, targetClassMap, erasedTargetNames);
        } else if (LIST_TYPE.equals(doubleErasure(elementType))) {
          parseBindMany(element, targetClassMap, erasedTargetNames);
        } else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
          error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
              ((TypeElement) element.getEnclosingElement()).getQualifiedName(),
              element.getSimpleName());
        } else {
          parseBindOne(element, targetClassMap, erasedTargetNames);
        }
      }
    

    可以看到parseBind会根据elementType的不同,进行不同的处理,我们就分析一下比较简单也是最通用的第19行的方法parseBindOne(element, targetClassMap, erasedTargetNames),第11,13行的parseBindMany()方法原理差不多。

    private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap,
          Set<TypeElement> erasedTargetNames) {
        boolean hasError = false;
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
        // Verify that the target type extends from View.
        TypeMirror elementType = element.asType();
        if (elementType.getKind() == TypeKind.TYPEVAR) {
          TypeVariable typeVariable = (TypeVariable) elementType;
          elementType = typeVariable.getUpperBound();
        }
        if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
          error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
              Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
          hasError = true;
        }
    
        // Assemble information on the field.
        int[] ids = element.getAnnotation(Bind.class).value();
        if (ids.length != 1) {
          error(element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)",
              Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
    
        if (hasError) {
          return;
        }
    
        int id = ids[0];
        BindingClass bindingClass = targetClassMap.get(enclosingElement);
        if (bindingClass != null) {
          ViewBindings viewBindings = bindingClass.getViewBinding(id);
          if (viewBindings != null) {
            Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
            if (iterator.hasNext()) {
              FieldViewBinding existingBinding = iterator.next();
              error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
                  Bind.class.getSimpleName(), id, existingBinding.getName(),
                  enclosingElement.getQualifiedName(), element.getSimpleName());
              return;
            }
          }
        } else {
          bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
        }
    
        String name = element.getSimpleName().toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
    
        FieldViewBinding binding = new FieldViewBinding(name, type, required);
        bindingClass.addField(id, binding);
    
        // Add the type-erased version to the valid binding targets set.
        erasedTargetNames.add(enclosingElement);
      }
    

    该方法是解析的核心方法,首先通过第4行TypeElement enclosingElement = (TypeElement) element.getEnclosingElement()获取使用注解的类的信息。我们在拿到使用注解的类的信息之后,会先验证注解的target的类型是否继承自view。接着第19行int[] ids = element.getAnnotation(Bind.class).value();获取注解标注的值。接着看32行到47行, 从targetClassMap中获取BindingClass实例,(BindingClass类是管理使用注解的实例的所有注解的信息以及实例本身的信息。在最后Butterknife会根据BindingClass类生成相应的辅助类。)如果没有成功,这会新创建一个BindingClass实例,对应于第46行bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement)。进去getOrCreateTargetClass(targetClassMap, enclosingElement)方法中看看:

    private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
          TypeElement enclosingElement) {
        BindingClass bindingClass = targetClassMap.get(enclosingElement);
        if (bindingClass == null) {
          String targetType = enclosingElement.getQualifiedName().toString();
          String classPackage = getPackageName(enclosingElement);
          String className = getClassName(enclosingElement, classPackage) + BINDING_CLASS_SUFFIX;
    
          bindingClass = new BindingClass(classPackage, className, targetType);
          targetClassMap.put(enclosingElement, bindingClass);
        }
        return bindingClass;
      }
    

    从上边的代码看创建BindingClass还是蛮简单的,需要注解对象的类型,然后包名,类名。我们来看看第7行String className = getClassName(enclosingElement, classPackage) + BINDING_CLASS_SUFFIX。其中BINDING_CLASS_SUFFIX是一个定义的常量private static final String BINDING_CLASS_SUFFIX = “$$ViewBinder”,上边介绍process()方法的时候说过,假如我们使AnnotationActivity使用了注解,那么Butterknife会生成一个AnnotationActivity$$ViewBinder的辅助类。对,辅助类的类名就是这样来的。接着上边的说在创建BindingClass后,将其放入targetClassMap。  
     
    回到parseBindOne()方法的第53,54行,将view的信息绑定在FieldViewBinding类的实例中,最后添加到上边生成的BindingClass实例中。到这里我们已经完成了基本的解析工作,并将所有使用注解的实例的信息存储在targetClassMap中。回到process()方法,为了方便我单独又把process()方法代码贴在下边:

    @Override
      public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    
        for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingClass bindingClass = entry.getValue();
    
          try {
            bindingClass.brewJava().writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
                    e.getMessage());
          }
        }
    
        return true;
      }
    

    通过刚刚分析Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env),我们获得到存储使用注解的实例信息的tagetClassMap,接下来的工作就是遍历tagetClassMap来生成相应的辅助类文件,这个工作是由第10行bindingClass.brewJava().writeTo(filer)完成的。我们知道每个类文件中都是有字符串拼接而成的,而brewJava()就是完成这个工作的,他是BindingClass类中的一个方法,主要就是做一些生成类的bind(),unbind(),注入View,和绑定事件,其中的细节有兴趣的可以自己看一看。

    JavaFile brewJava() {
        TypeSpec.Builder result = TypeSpec.classBuilder(className)
            .addModifiers(PUBLIC)
            .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
    
        if (parentViewBinder != null) {
          result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
              TypeVariableName.get("T")));
        } else {
          result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
        }
    
        result.addMethod(createBindMethod());
    
        if (hasUnbinder()) {
          // Create unbinding class.
          result.addType(createUnbinderClass());
          // Now we need to provide child classes to access and override unbinder implementations.
          createUnbinderInternalAccessMethods(result);
        }
    
        return JavaFile.builder(classPackage, result.build())
            .addFileComment("Generated code from Butter Knife. Do not modify!")
            .build();
      }
    

    做完这些工作之后,就可以生成java文件了,看writeTo(Filer filer):

    /** Writes this to {@code filer}. */
      public void writeTo(Filer filer) throws IOException {
        String fileName = packageName.isEmpty()
            ? typeSpec.name
            : packageName + "." + typeSpec.name;
        List<Element> originatingElements = typeSpec.originatingElements;
        JavaFileObject filerSourceFile = filer.createSourceFile(fileName,
            originatingElements.toArray(new Element[originatingElements.size()]));
        try (Writer writer = filerSourceFile.openWriter()) {
          writeTo(writer);
        } catch (Exception e) {
          try {
            filerSourceFile.delete();
          } catch (Exception ignored) {
          }
          throw e;
        }
      }
    

    生成的java文件在编译后会生成相应的class文件,下图是我上文提到的使用注解的AnnotationActivity和生成的辅助类AnnotationActivity$$ViewBinder。看图:


    下边是两个文件的源码,  
    AnnotationActivity$$ViewBinder
    import android.view.View;
    import android.widget.Button;
    import butterknife.ButterKnife.Finder;
    import butterknife.ButterKnife.ViewBinder;
    import butterknife.internal.DebouncingOnClickListener;
    
    public class AnnotationActivity$$ViewBinder<T extends AnnotationActivity>
      implements ButterKnife.ViewBinder<T>
    {
      public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
      {
        View localView = (View)paramFinder.findRequiredView(paramObject, 2131558508, "field 'button' and method 'Onclick'");
        paramT.button = ((Button)paramFinder.castView(localView, 2131558508, "field 'button'"));
        localView.setOnClickListener(new DebouncingOnClickListener()
        {
          public void doClick(View paramAnonymousView)
          {
            paramT.Onclick();
          }
        });
      }
    
      public void unbind(T paramT)
      {
        paramT.button = null;
      }
    }
    

    AnnotationActivity

    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.Button;
    import android.widget.Toast;
    import butterknife.Bind;
    import butterknife.OnClick;
    
    public class AnnotationActivity extends Activity
    {
    
      @Bind({2131558508})
      Button button;
    
      @OnClick({2131558508})
      public void Onclick()
      {
        Toast.makeText(this, "helloWorld", 0).show();
      }
    
      protected void onCreate(Bundle paramBundle)
      {
        super.onCreate(paramBundle);
        setContentView(2130968601);
        ButterKnife.bind(this);
      }
    
    }
    

    从上边的代码可以看到AnnotationActivity$$ViewBinder实现了ButterKnife.ViewBinder接口,他的定义是这样的

    package butterknife.internal;
    
    public interface ViewBinder<T> {
      void bind(Finder finder, T target, Object source);
    }
    

    这个是很重要的,后边会说到。接下来我们看他们之间是怎么工作的。我们平时使用的时候都是ButterKnife.bind()方法来绑定上下文的。那么我们就从这开始分析吧。 ButterKnife.bind()有还几个重载的方法,分别对应不同的实例,就像我们可以绑定Activity,Fragment,Viewholder等,我们以Activity为例

    /**
       * Bind annotated fields and methods in the specified {@code target} using the {@code source}
       * {@link Activity} as the view root.
       *
       * @param target Target class for view binding.
       * @param source Activity on which IDs will be looked up.
       */
      public static void bind(@NonNull Object target, @NonNull Activity source) {
        bind(target, source, Finder.ACTIVITY);
      }
    

    这里的Finder是个enum类,有VIEW,ACTIVITY,DIALOG类型,我们这里传的是ACTIVITY类型。接着看:

    static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
        Class<?> targetClass = target.getClass();
        try {
          if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
          ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
          viewBinder.bind(finder, target, source);
        } catch (Exception e) {
          throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
        }
      }
    

    第5行findViewBinderForClass(targetClass)获取与此Activity对应的编译阶段生成java类的实例:

    @NonNull
      private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
          throws IllegalAccessException, InstantiationException {
        ViewBinder<Object> viewBinder = BINDERS.get(cls);
        if (viewBinder != null) {
          if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
          return viewBinder;
        }
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
          if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
          return NOP_VIEW_BINDER;
        }
        try {
          Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
          //noinspection unchecked
          viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
          if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
        } catch (ClassNotFoundException e) {
          if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
          viewBinder = findViewBinderForClass(cls.getSuperclass());
        }
        BINDERS.put(cls, viewBinder);
        return viewBinder;
      }
    

    首先会先在BINDERS中获取对应的实例,BINDERS是一个以Class为键,ViewBinder为值得Map。如果没有获取到实例那么通过15-22行代码通过反射生成ViewBinder实例。对你没有看错,这里大家会不会有疑问,不是说使用反射机制很慢吗,为什么ButterKnife还使用,如果你读过之前推荐的文章应该知道答案。如果没有注意,也没有关系,我们自己看代码也可以得到答案,通过反射确实是会有性能问题,但是通过反射可以自动的生成实例,而不需要开发者手动生成。并且请看上边代码第23行BINDERS.put(cls, viewBinder),将通过反射生成的实例,放在了BINDERS中,也就是说,只是第一次使用了反射,后边直接从BINDERS取就可以了。这很好的解决了一下性能问题。
      
    继续向下,回到bind()方法第6行viewBinder.bind(finder, target, source),调用了ViewBinder接口的bind方法,记得之前说过ViewBinder接口很重要,就是在这里。因为我们生成的AnnotationActivity$$ViewBinder的那个辅助类是实现了ViewBinder的接口了的,所以这里bind方法的具体实现是在AnnotationActivity$$ViewBinder中的bind()中。为了方便,我把之前的代码贴过来

    public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
      {
        View localView = (View)paramFinder.findRequiredView(paramObject, 2131558508, "field 'button' and method 'Onclick'");
        paramT.button = ((Button)paramFinder.castView(localView, 2131558508, "field 'button'"));
        localView.setOnClickListener(new DebouncingOnClickListener()
        {
          public void doClick(View paramAnonymousView)
          {
            paramT.Onclick();
          }
        });
      }
    

    第3行会执行Finder这个enum类的findRequiredView方法,到这儿你就可以彻底明白了真相。

    public <T> T findRequiredView(Object source, int id, String who) {
        T view = findOptionalView(source, id, who);
        if (view == null) {
          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.");
        }
        return view;
      }
    
      public <T> T findOptionalView(Object source, int id, String who) {
        View view = findView(source, id);
        return castView(view, id, who);
      }
    

    findView是一个抽象方法,会根据我们之前绑定上下文的类型来执行,我们之前传的是Finder.ACTIVITY,所以会执行以下代码:

     ACTIVITY {
          @Override 
          protected View findView(Object source, int id) {
          return ((Activity) source).findViewById(id);
        }
    

    哈哈,终于找到了,findViewById()。大家都懂。好了,我们在回到AnnotationActivity$$ViewBinder的bind()方法看看第4-11行:

    paramT.button = ((Button)paramFinder.castView(localView, 2131558508, "field 'button'"));
        localView.setOnClickListener(new DebouncingOnClickListener()
        {
          public void doClick(View paramAnonymousView)
          {
            paramT.Onclick();
          }
        });
    

    第1行这里有一个细节paramT.button,对应于AnnotationActivity就是AnnotationActivity.this.button这里解释了ButterKnife为什么要求使用注解的成员要使用public和protected,因为如果使用private的话,上边是直接找不到button的,只能通过反射,这是万万不能忍受的。   接着看代码,喜大泪奔,熟悉的代码!不过这里还是要说一下这里的DebouningOnClickListener实现了View.OnClickListener的接口。

    总结一下

    ButterKnife的ButterKnifeProcessor在编译时会扫描你的Java代码中所有使用@Bind,@OnClick等注解,如果发现存在注解,那么通过一系列的解析工作,生成一个类似AnnotationActivity$$ViewBinder(className$$ViewBinder)的Java类,这个类实现了ViewBinder接口。这个生成类中实现了各注解对应的代码像@Bind最终会执行findViewById(),@OnClick最终会执行setOnClickListener()。我们在调用ButterKnife.bind()时自动的加载上下文对应的生成的实例。好了,就到这儿吧,水平有限,如果有什么不对地方,还请各位留言指正。

    相关文章

      网友评论

        本文标题:ButterKnife源码剖析

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