ButterKnife核心源码解析

2017-10-17  本文已影响12人  走川

Butterknife的源码之前只是单纯的知道大致的一个思路,没有具体看过他的实现步骤。因作此文已记之。

Ox1 init :对外暴露的初始函数

 @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
   //获取sdk版本 ,注解器的配置在  getSupportedOptions() 中查看
    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.");
      }
    }
   //是否debug模式
    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) {
    }
  }

 //配置了注解处理器所需要的入参
  @Override public Set<String> getSupportedOptions() {
    return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
  }

getSupportedIptions的配置项在主工程的build.gradle

android {
     javaCompileOptions {
         annotationProcessorOptions {
            arguments = [debug: "2333"] //参数只能是字符串
         }
      }
  }

总结以上代码:

  1. 获取配置项
  2. 获取相关的工具类的实例

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

      //生成java代码
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        //创建java文件
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

以上的流程有两个疑惑点:

  1. BindingSet:这个类是作用是?
  2. findAndParseTargets:如何获取到注解内容的结果的?

我们先来看看BindingSet的核心代码: constructor & brewJava

  //通过Budilder 构造,对参数进行配置
  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();
  }

总结以上代码:

BindingSet 大致的作用就是

  1. 一个文件所需要的所有绑定关系
  2. 生成的代码的函数(通过JavaPoet

所以剩下的就在 findAndParseTargets 这个方法内了

  private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    scanForRClasses(env);

   // Process each @BindColor element.
    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceColor(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindColor.class, e);
      }
    }

    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

  (以下省略其他 类似 @BindXXX 代码)
     ...
    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }

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

总结以上代码:

  1. 处理注解的信息
  2. 将结果组合成bindingMap

以上的 parseXXX 方法实现逻辑大体一致,因此我们只需要看其中的一个细分方法就ok了

Ox3 parseBindView :处理 @BindView注解 信息

为了让大家更好的理解,我们先写一个demo

 package com.example
  
 public class MainActivity extends AppCompatActivity {

   @BindView(R.id.text)
   TextView tvTest
     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
    }
  }

以下将会以上面的demo的情况来进行讲解

  private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {

    //获取 MainActivity
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    //判断 修饰符不为private 字段;注解不使用在Android、java的包内
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);

    //获取 TextView
    TypeMirror elementType = element.asType();

    //todo
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    //获取 com.example.MainActivity
    Name qualifiedName = enclosingElement.getQualifiedName();
    //获取 tvTest
    Name simpleName = element.getSimpleName();

    //判断 是否为View的子类;是否为接口类型
    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;
    }

    //获取注解上的id
    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);
  }
  
上一篇下一篇

猜你喜欢

热点阅读