美文网首页
ButterKnife源码分析

ButterKnife源码分析

作者: gogoingmonkey | 来源:发表于2018-08-03 16:12 被阅读24次

    原理:

    APT(Annotation Processing Tool)编译时解析技术(现在已经改成了谷歌的更强大的annotationProcessor,APT已经停止更新了)就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用方法完成绑定


    Butterknife.png

    ButterKnife源码解析

    不同版本源码区别比较大,这里看的是8.6.0版本,对比了和7.0.1 入口差很多!

    直接就从 ButterKnife.bind(this)入手吧,点进来看看:

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

    再点到createBinding(target, sourceView)里面看看:

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

    createBinding()方法主要就是拿到我们绑定的Activity的Class,然后通过Constructor构造器获取一个Unbinder子类的构造方法,然后在调用newInstance(target, source)通过构造方法获取到Unbinder子类的一个实例,这里传入两个参数,说明构造方法里需要两个参数。我们打开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;
      }
    
    

    上面的BINDINGS是一个保存了Class为key,Class_ViewBinding为Value的一个LinkedHashMap,主要是做一下缓存,提高下次再来bind的性能。
    第14行clsName 是我们传入要绑定的Activity类名,第16行通过反射调用构造方法,这里相当于拿到了Activity_ViewBinding这个类的实例。其实从类名可以看出来,相当于Activity的一个辅助类,这时候我们就要问了,我们在用的时候没有声明这个类啊?从哪里来的? 不要方,其实它就是我们在之前讲原理的时候说到的AbstractProcessor在编译的时候生成的一个类,我们后面再来看它,现在我们继续往下面分析。
    前面我们说到,这个方法里面用linkhashMap做了下缓存,所以在下边,就把刚刚反射的bindingCtor作为value,Class作为key加入这个LinkedHashMap,下次再绑定这个类的时候,就直接在方法的开始的时候取出来用

    再看下面build 生成的类


    image.png

    刚才说的Unbinder的一个子类,看这里的类名。现在应该懂了。它刚好是实现了Unbinder接口。之前说了通过反射拿到了Activity_ViewBinding这个类的构造方法即通过调用getConstructor(cls, View.class)方法,然后通过newInstance(target, source)方法创建实例,这里传入的两个参数就是我们MainActivity_ViewBinding(final MainActivity target, View source)里面的两个参数。因为我们可以在Activity中使用butterknife,也可以在Fragment和Adapter等中使用butterknife,那么在不同的地方使用butterknife,这个target也就不同。我们接着看里面的findRequiredView方法:

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

    还是使用的findViewById。
    返回上面的MainActivity_ViewBinding代码,首先调用了findRequiredView方法,其实这个方法最后经过处理就是调用了findViewById方法,拿到相应的view,然后再赋值给target.tv,刚说了target就是那个要绑定的Activity,这里通过 target.tv 这样的调用方式,说明了Activity中不能把TextView设置为private,不然会报错,其实这里可以用反射来拿到textView的,这里应该也是为了性能着想。最后setOnClickListener,DebouncingOnClickListener这个Listener其实也是实现了View.OnClickListener 方法,然后在OnClick里面调用了doClick方法。

    Butterknife在编译的时候生成代码的原理

    com.jakewharton:butterknife-compiler 就是自定义的注解处理器,我们在 Gradle 中注册使用它。
    然而我在项目结构中找了很久也没有找到这个库的文件,有可能是在编译时才去访问的,如果需要可以在 GitHub 中找到:
    butterknife-compiler
    我们看看ButterKnifeProcessor这个类:

    
    @Override public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
    
        String sdk = env.getOptions().get(OPTION_SDK_INT);
        if (sdk != null) {
          try {
            this.sdk = Integer.parseInt(sdk);
          } catch (NumberFormatException e) {
            env.getMessager()
                .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
                    + sdk
                    + "'. Falling back to API 1 support.");
          }
        }
    
        debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
    
        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
        try {
          trees = Trees.instance(processingEnv);
        } catch (IllegalArgumentException ignored) {
        }
      }
    

    int()方法里面进来判断了最低的支持的sdk版本。ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。Types是用来处理TypeMirror的工具类,Filer用来创建生成辅助文件。至于ElementUtils嘛,其实ButterKnifeProcessor在运行的时候,会扫描所有的Java源文件,然后每一个Java源文件的每一个部分都是一个Element,比如一个包、类或者方法。

    @Override public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
          types.add(annotation.getCanonicalName());
        }
        return types;
      }
    
      private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
    
        annotations.add(BindAnim.class);
        annotations.add(BindArray.class);
        annotations.add(BindBitmap.class);
        annotations.add(BindBool.class);
        annotations.add(BindColor.class);
        annotations.add(BindDimen.class);
        annotations.add(BindDrawable.class);
        annotations.add(BindFloat.class);
        annotations.add(BindFont.class);
        annotations.add(BindInt.class);
        annotations.add(BindString.class);
        annotations.add(BindView.class);
        annotations.add(BindViews.class);
        annotations.addAll(LISTENERS);
    
        return annotations;
      }
    

    getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注册给哪些注解的。我们可以看到,在源代码里面,作者一个一个地把Class文件加到那个LinkedHashSet里面,然后再把LISTENERS也全部加进去。

    其实整个类最重要的是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;
      }
    

    这个方法的作用主要是扫描、评估和处理我们程序中的注解,然后生成Java文件,也就是前面说的MainActivity_ViewBinding。首先一进这个函数就调用了findAndParseTargets方法,我们就去看看findAndParseTargets方法到底做了什么:

    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
        scanForRClasses(env);
    
        // Process each @BindAnim element.
        for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
            parseResourceAnimation(element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindAnim.class, e);
          }
        }
    ........
    ........
    ........
    // Associate superclass binders with their subclass binders. This is a queue-based tree walk
        // which starts at the roots (superclasses) and walks to the leafs (subclasses).
        Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
            new ArrayDeque<>(builderMap.entrySet());
        Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
        while (!entries.isEmpty()) {
          Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
    
          TypeElement type = entry.getKey();
          BindingSet.Builder builder = entry.getValue();
    
          TypeElement parentType = findParentType(type, erasedTargetNames);
          if (parentType == null) {
            bindingMap.put(type, builder.build());
          } else {
            BindingSet parentBinding = bindingMap.get(parentType);
            if (parentBinding != null) {
              builder.setParent(parentBinding);
              bindingMap.put(type, builder.build());
            } else {
              // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
              entries.addLast(entry);
            }
          }
        }
    
        return bindingMap;
    

    这个方法的代码非常多,这里只贴出一部分,这个方法的主要的流程如下:

    扫描所有具有注解的类,然后根据这些类的信息生成BindingSet,最后生成以TypeElement为键,BindingSet为值的键值对。
    循环遍历这个键值对,根据TypeElement和BindingSet里面的信息生成对应的java类。例如AnnotationActivity生成的类即为MainActivity_ViewBinding类。

    这里我们可以看看BindingSet里面的代码:

    final class BindingSet {
      static final ClassName UTILS = ClassName.get("butterknife.internal", "Utils");
      private static final ClassName VIEW = ClassName.get("android.view", "View");
      private static final ClassName CONTEXT = ClassName.get("android.content", "Context");
      private static final ClassName RESOURCES = ClassName.get("android.content.res", "Resources");
      private static final ClassName UI_THREAD =
          ClassName.get("android.support.annotation", "UiThread");
      private static final ClassName CALL_SUPER =
          ClassName.get("android.support.annotation", "CallSuper");
      private static final ClassName SUPPRESS_LINT =
          ClassName.get("android.annotation", "SuppressLint");
      private static final ClassName UNBINDER = ClassName.get("butterknife", "Unbinder");
      static final ClassName BITMAP_FACTORY = ClassName.get("android.graphics", "BitmapFactory");
      static final ClassName CONTEXT_COMPAT =
          ClassName.get("android.support.v4.content", "ContextCompat");
      static final ClassName ANIMATION_UTILS =
              ClassName.get("android.view.animation", "AnimationUtils");
    
      private final TypeName targetTypeName;
      private final ClassName bindingClassName;
      private final boolean isFinal;
      private final boolean isView;
      private final boolean isActivity;
      private final boolean isDialog;
      private final ImmutableList<ViewBinding> viewBindings;
      private final ImmutableList<FieldCollectionViewBinding> collectionBindings;
      private final ImmutableList<ResourceBinding> resourceBindings;
      private final BindingSet parentBinding;
    
      private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
          boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings,
          ImmutableList<FieldCollectionViewBinding> collectionBindings,
          ImmutableList<ResourceBinding> resourceBindings, BindingSet parentBinding) {
        this.isFinal = isFinal;
        this.targetTypeName = targetTypeName;
        this.bindingClassName = bindingClassName;
        this.isView = isView;
        this.isActivity = isActivity;
        this.isDialog = isDialog;
        this.viewBindings = viewBindings;
        this.collectionBindings = collectionBindings;
        this.resourceBindings = resourceBindings;
        this.parentBinding = parentBinding;
      }
    
      JavaFile brewJava(int sdk, boolean debuggable) {
        return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
            .addFileComment("Generated code from Butter Knife. Do not modify!")
            .build();
      }
    
      private TypeSpec createType(int sdk, boolean debuggable) {
        TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
            .addModifiers(PUBLIC);
        if (isFinal) {
          result.addModifiers(FINAL);
        }
        ........
        ........
        ........
    
      static final class Builder {
        private final TypeName targetTypeName;
        private final ClassName bindingClassName;
        private final boolean isFinal;
        private final boolean isView;
        private final boolean isActivity;
        private final boolean isDialog;
    
        private BindingSet parentBinding;
    
        private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
        private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings =
            ImmutableList.builder();
        private final ImmutableList.Builder<ResourceBinding> resourceBindings = ImmutableList.builder();
    
        private Builder(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
            boolean isView, boolean isActivity, boolean isDialog) {
          this.targetTypeName = targetTypeName;
          this.bindingClassName = bindingClassName;
          this.isFinal = isFinal;
          this.isView = isView;
          this.isActivity = isActivity;
          this.isDialog = isDialog;
        }
    
        void addField(Id id, FieldViewBinding binding) {
          getOrCreateViewBindings(id).setFieldBinding(binding);
        }
    
        void addFieldCollection(FieldCollectionViewBinding binding) {
          collectionBindings.add(binding);
        }
    
        boolean addMethod(
            Id id,
            ListenerClass listener,
            ListenerMethod method,
            MethodViewBinding binding) {
          ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
          if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {
            return false;
          }
          viewBinding.addMethodBinding(listener, method, binding);
          return true;
        }
    
        void addResource(ResourceBinding binding) {
          resourceBindings.add(binding);
        }
    
        void setParent(BindingSet parent) {
          this.parentBinding = parent;
        }
    
        String findExistingBindingName(Id id) {
          ViewBinding.Builder builder = viewIdMap.get(id);
          if (builder == null) {
            return null;
          }
          FieldViewBinding fieldBinding = builder.fieldBinding;
          if (fieldBinding == null) {
            return null;
          }
          return fieldBinding.getName();
        }
    
        private ViewBinding.Builder getOrCreateViewBindings(Id id) {
          ViewBinding.Builder viewId = viewIdMap.get(id);
          if (viewId == null) {
            viewId = new ViewBinding.Builder(id);
            viewIdMap.put(id, viewId);
          }
          return viewId;
        }
    
        BindingSet build() {
          ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
          for (ViewBinding.Builder builder : viewIdMap.values()) {
            viewBindings.add(builder.build());
          }
          return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
              viewBindings.build(), collectionBindings.build(), resourceBindings.build(),
              parentBinding);
        }
      }
    }
    

    这个类的代码也非常多,所以我们也只贴一部分,可以自己去看看源码,这个BindingSet是管理了所有关于这个注解的一些信息还有实例本身的信息。

    因为我们之前用的例子是绑定的一个View,所以我们就只贴了解析View的代码。好吧,这里遍历了所有带有@BindView的Element,然后对每一个Element进行解析,也就进入了parseBindView这个方法中

    private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
          Set<TypeElement> erasedTargetNames) {
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
        // Start by verifying common generated code restrictions.
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
            || isBindingInWrongPackage(BindView.class, element);
    
        // Verify that the target type extends from View.
        TypeMirror elementType = element.asType();
        if (elementType.getKind() == TypeKind.TYPEVAR) {
          TypeVariable typeVariable = (TypeVariable) elementType;
          elementType = typeVariable.getUpperBound();
        }
        Name qualifiedName = enclosingElement.getQualifiedName();
        Name simpleName = element.getSimpleName();
        if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
          if (elementType.getKind() == TypeKind.ERROR) {
            note(element, "@%s field with unresolved type (%s) "
                    + "must elsewhere be generated as a View or interface. (%s.%s)",
                BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
          } else {
            error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
                BindView.class.getSimpleName(), qualifiedName, simpleName);
            hasError = true;
          }
        }
    
        if (hasError) {
          return;
        }
    
        // Assemble information on the field.
        int id = element.getAnnotation(BindView.class).value();
    
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        QualifiedId qualifiedId = elementToQualifiedId(element, id);
        if (builder != null) {
          String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
          if (existingBindingName != null) {
            error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
                BindView.class.getSimpleName(), id, existingBindingName,
                enclosingElement.getQualifiedName(), element.getSimpleName());
            return;
          }
        } else {
          builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }
    
        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
    
        builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
    
        // Add the type-erased version to the valid binding targets set.
        erasedTargetNames.add(enclosingElement);
      }
    
    

    然后这里从一进入这个方法到

    int id = element.getAnnotation(BindView.class).value();
    

    都是在拿到注解信息,然后验证注解的target的类型是否继承自view,然后上面这一行代码获得我们要绑定的View的id,再从builderMap里面取出BindingSet.Builder对象(这个BindingSet是管理了所有关于这个注解的一些信息还有实例本身的信息,其实最后是通过BindingSet来生成java代码的,上面也已经看了BindingSet的代码),如果builderMap里面不存在的话,就在

    builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    

    这里生成一个,我们进去看一下getOrCreateBindingBuilder:

    private BindingSet.Builder getOrCreateBindingBuilder(
          Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        if (builder == null) {
          builder = BindingSet.newBuilder(enclosingElement);
          builderMap.put(enclosingElement, builder);
        }
        return builder;
      }
    

    这里面其实很简单,就是获取一些这个注解所修饰的变量的一些信息,然后把这个解析后的builder加入到builderMap里面。

    返回刚刚的parseBindView中,根据view的信息生成一个FieldViewBinding,最后添加到上边生成的builder实例中。这里基本完成了解析工作。最后回到findAndParseTargets中:

    // Associate superclass binders with their subclass binders. This is a queue-based tree walk
        // which starts at the roots (superclasses) and walks to the leafs (subclasses).
        Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
            new ArrayDeque<>(builderMap.entrySet());
        Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
        while (!entries.isEmpty()) {
          Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
    
          TypeElement type = entry.getKey();
          BindingSet.Builder builder = entry.getValue();
    
          TypeElement parentType = findParentType(type, erasedTargetNames);
          if (parentType == null) {
            bindingMap.put(type, builder.build());
          } else {
            BindingSet parentBinding = bindingMap.get(parentType);
            if (parentBinding != null) {
              builder.setParent(parentBinding);
              bindingMap.put(type, builder.build());
            } else {
              // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
              entries.addLast(entry);
            }
          }
        }
    

    这里主要的工作是建立上面的绑定的所有的实例的解绑的关系,因为我们绑定了,最后在代码中还是会解绑的。这里预先处理好了这些关系。
    回到我们的process中, 现在解析完了annotation,该生成java文件了,再看看代码:

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

    遍历刚刚得到的bindingMap,然后再一个一个地通过

    javaFile.writeTo(filer);
    

    来生成java文件。然而生成的java文件也是根据上面的信息来用字符串拼接起来的,然而这个工作在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();
      }
    

    最后通过writeTo(Filer filer)生成java源文件。

    上面的是一个大体流程,下面梳理下重点:

    1Butterknife是编译期生成代码方法,对运行没副作用,不像反射Afinal框架。
    2.我们在MainActivity中写的@Bindview(R.id.text) TextView tv;自动生成了

     target.bt = (TextView)Utils.findRequiredViewAsType(source, 2131427423, "field \'bt\'", TextView.class);
    

    这个过程就是注解处理器完成的。Butterknife是怎么实现的,

    @AutoServer(Processor.class)
    public final class ButterknifeProcessor extends AbstructProcessor{
      .....
    }
    

    AutoServer这个是谷歌的一个开源项目,自动生成 而 Butterknife的核心就是他的process(){
    1.findAnd ParseTagets() 去解析注解
    2.去遍历1得到的map集合,
    3.拿到上面的键值对的值也就是binding的class 再调writeTo方法,正真生成代码

    }

    相关文章

      网友评论

          本文标题:ButterKnife源码分析

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