ButterKnife源碼分析

原理:

APT(Annotation Processing Tool)編譯時(shí)解析技術(shù)(現(xiàn)在已經(jīng)改成了谷歌的更強(qiáng)大的annotationProcessor乒融,APT已經(jīng)停止更新了)就是你聲明的注解的生命周期為CLASS,然后繼承AbstractProcessor類斋配。繼承這個類后,在編譯的時(shí)候真椿,編譯器會掃描所有帶有你要處理的注解的類,然后再調(diào)用AbstractProcessor的process方法,對注解進(jìn)行處理崇呵,那么我們就可以在處理的時(shí)候出爹,動態(tài)生成綁定事件或者控件的java代碼庄吼,然后在運(yùn)行的時(shí)候,直接調(diào)用方法完成綁定


Butterknife.png

ButterKnife源碼解析

不同版本源碼區(qū)別比較大严就,這里看的是8.6.0版本总寻,對比了和7.0.1 入口差很多!

直接就從 ButterKnife.bind(this)入手吧,點(diǎn)進(jìn)來看看:

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

再點(diǎn)到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構(gòu)造器獲取一個Unbinder子類的構(gòu)造方法废菱,然后在調(diào)用newInstance(target, source)通過構(gòu)造方法獲取到Unbinder子類的一個實(shí)例,這里傳入兩個參數(shù)抖誉,說明構(gòu)造方法里需要兩個參數(shù)殊轴。我們打開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行通過反射調(diào)用構(gòu)造方法,這里相當(dāng)于拿到了Activity_ViewBinding這個類的實(shí)例我磁。其實(shí)從類名可以看出來孽文,相當(dāng)于Activity的一個輔助類,這時(shí)候我們就要問了夺艰,我們在用的時(shí)候沒有聲明這個類坝罂蕖?從哪里來的郁副? 不要方减牺,其實(shí)它就是我們在之前講原理的時(shí)候說到的AbstractProcessor在編譯的時(shí)候生成的一個類,我們后面再來看它存谎,現(xiàn)在我們繼續(xù)往下面分析拔疚。
前面我們說到,這個方法里面用linkhashMap做了下緩存既荚,所以在下邊稚失,就把剛剛反射的bindingCtor作為value,Class作為key加入這個LinkedHashMap,下次再綁定這個類的時(shí)候恰聘,就直接在方法的開始的時(shí)候取出來用

再看下面build 生成的類


image.png

剛才說的Unbinder的一個子類句各,看這里的類名∥迹現(xiàn)在應(yīng)該懂了。它剛好是實(shí)現(xiàn)了Unbinder接口诫钓。之前說了通過反射拿到了Activity_ViewBinding這個類的構(gòu)造方法即通過調(diào)用getConstructor(cls, View.class)方法旬昭,然后通過newInstance(target, source)方法創(chuàng)建實(shí)例,這里傳入的兩個參數(shù)就是我們MainActivity_ViewBinding(final MainActivity target, View source)里面的兩個參數(shù)菌湃。因?yàn)槲覀兛梢栽贏ctivity中使用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代碼,首先調(diào)用了findRequiredView方法下愈,其實(shí)這個方法最后經(jīng)過處理就是調(diào)用了findViewById方法纽绍,拿到相應(yīng)的view,然后再賦值給target.tv势似,剛說了target就是那個要綁定的Activity拌夏,這里通過 target.tv 這樣的調(diào)用方式,說明了Activity中不能把TextView設(shè)置為private履因,不然會報(bào)錯障簿,其實(shí)這里可以用反射來拿到textView的,這里應(yīng)該也是為了性能著想栅迄。最后setOnClickListener站故,DebouncingOnClickListener這個Listener其實(shí)也是實(shí)現(xiàn)了View.OnClickListener 方法,然后在OnClick里面調(diào)用了doClick方法毅舆。

Butterknife在編譯的時(shí)候生成代碼的原理

com.jakewharton:butterknife-compiler 就是自定義的注解處理器西篓,我們在 Gradle 中注冊使用它。
然而我在項(xiàng)目結(jié)構(gòu)中找了很久也沒有找到這個庫的文件憋活,有可能是在編譯時(shí)才去訪問的岂津,如果需要可以在 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()方法里面進(jìn)來判斷了最低的支持的sdk版本。ProcessingEnviroment參數(shù)提供很多有用的工具類Elements, Types和Filer悦即。Types是用來處理TypeMirror的工具類寸爆,F(xiàn)iler用來創(chuàng)建生成輔助文件。至于ElementUtils嘛盐欺,其實(shí)ButterKnifeProcessor在運(yùn)行的時(shí)候,會掃描所有的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也全部加進(jìn)去安拟。

其實(shí)整個類最重要的是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。首先一進(jìn)這個函數(shù)就調(diào)用了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;

這個方法的代碼非常多拙泽,這里只貼出一部分,這個方法的主要的流程如下:

掃描所有具有注解的類裸燎,然后根據(jù)這些類的信息生成BindingSet顾瞻,最后生成以TypeElement為鍵,BindingSet為值的鍵值對。
循環(huán)遍歷這個鍵值對德绿,根據(jù)TypeElement和BindingSet里面的信息生成對應(yīng)的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是管理了所有關(guān)于這個注解的一些信息還有實(shí)例本身的信息秒裕。

因?yàn)槲覀冎坝玫睦邮墙壎ǖ囊粋€View袱蚓,所以我們就只貼了解析View的代碼。好吧几蜻,這里遍歷了所有帶有@BindView的Element喇潘,然后對每一個Element進(jìn)行解析,也就進(jìn)入了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);
  }

然后這里從一進(jìn)入這個方法到

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

都是在拿到注解信息梭稚,然后驗(yàn)證注解的target的類型是否繼承自view颖低,然后上面這一行代碼獲得我們要綁定的View的id,再從builderMap里面取出BindingSet.Builder對象(這個BindingSet是管理了所有關(guān)于這個注解的一些信息還有實(shí)例本身的信息弧烤,其實(shí)最后是通過BindingSet來生成java代碼的忱屑,上面也已經(jīng)看了BindingSet的代碼),如果builderMap里面不存在的話暇昂,就在

builder = getOrCreateBindingBuilder(builderMap, enclosingElement);

這里生成一個莺戒,我們進(jìn)去看一下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;
  }

這里面其實(shí)很簡單,就是獲取一些這個注解所修飾的變量的一些信息急波,然后把這個解析后的builder加入到builderMap里面从铲。

返回剛剛的parseBindView中,根據(jù)view的信息生成一個FieldViewBinding澄暮,最后添加到上邊生成的builder實(shí)例中名段。這里基本完成了解析工作阱扬。最后回到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);
        }
      }
    }

這里主要的工作是建立上面的綁定的所有的實(shí)例的解綁的關(guān)系,因?yàn)槲覀兘壎松毂伲詈笤诖a中還是會解綁的麻惶。這里預(yù)先處理好了這些關(guān)系。
回到我們的process中信夫, 現(xiàn)在解析完了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文件也是根據(jù)上面的信息來用字符串拼接起來的脐彩,然而這個工作在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源文件。

上面的是一個大體流程姊扔,下面梳理下重點(diǎn):

1Butterknife是編譯期生成代碼方法惠奸,對運(yùn)行沒副作用,不像反射Afinal框架恰梢。
2.我們在MainActivity中寫的@Bindview(R.id.text) TextView tv;自動生成了

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

這個過程就是注解處理器完成的佛南。Butterknife是怎么實(shí)現(xiàn)的,

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

AutoServer這個是谷歌的一個開源項(xiàng)目嵌言,自動生成 而 Butterknife的核心就是他的process(){
1.findAnd ParseTagets() 去解析注解
2.去遍歷1得到的map集合嗅回,
3.拿到上面的鍵值對的值也就是binding的class 再調(diào)writeTo方法,正真生成代碼

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摧茴,一起剝皮案震驚了整個濱河市绵载,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苛白,老刑警劉巖娃豹,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異购裙,居然都是意外死亡懂版,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門躏率,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躯畴,“玉大人,你說我怎么就攤上這事薇芝∨畛” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵夯到,是天一觀的道長嚷缭。 經(jīng)常有香客問我,道長黄娘,這世上最難降的妖魔是什么峭状? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮逼争,結(jié)果婚禮上优床,老公的妹妹穿的比我還像新娘。我一直安慰自己誓焦,他們只是感情好胆敞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杂伟,像睡著了一般移层。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赫粥,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天观话,我揣著相機(jī)與錄音,去河邊找鬼越平。 笑死频蛔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秦叛。 我是一名探鬼主播晦溪,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挣跋!你這毒婦竟也來了三圆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤避咆,失蹤者是張志新(化名)和其女友劉穎舟肉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牌借,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡度气,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膨报。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磷籍。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖现柠,靈堂內(nèi)的尸體忽然破棺而出院领,到底是詐尸還是另有隱情,我是刑警寧澤够吩,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布比然,位于F島的核電站,受9級特大地震影響周循,放射性物質(zhì)發(fā)生泄漏强法。R本人自食惡果不足惜万俗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饮怯。 院中可真熱鬧闰歪,春花似錦、人聲如沸蓖墅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽论矾。三九已至教翩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贪壳,已是汗流浹背饱亿。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寥袭,地道東北人路捧。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像传黄,于是被迫代替她去往敵國和親杰扫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 博文出處:ButterKnife源碼分析膘掰,歡迎大家關(guān)注我的博客章姓,謝謝! 0x01 前言 在程序開發(fā)的過程中识埋,總會有...
    俞其榮閱讀 2,027評論 1 18
  • butterknife注解框架相信很多同學(xué)都在用凡伊,但是你真的了解它的實(shí)現(xiàn)原理嗎?那么今天讓我們來看看它到底是怎么實(shí)...
    打不死的小強(qiáng)qz閱讀 645評論 0 4
  • 主目錄見:Android高級進(jìn)階知識(這是總目錄索引)?前面我們已經(jīng)講完[編譯期注解的使用例子]大家應(yīng)該對這個流程...
    ZJ_Rocky閱讀 1,484評論 0 8
  • 我們每一個人都是要慢慢長大的窒舟,這是一個過程系忙,這幾天被某件事弄得有點(diǎn)不開心,然后開始反思自己惠豺,自己真的有嘴上說的...
    不吃肥肉不挑食的孩子閱讀 218評論 0 2
  • 建國至今银还,中國各方面都取得了偉大成就,創(chuàng)造了人類歷史上的奇跡洁墙,世界為之驚嘆蛹疯,世人開始矚目東方中國,在世界面臨重重危...
    77f8735e5e7c閱讀 521評論 0 2