美文网首页
ButterKnife核心源码解析

ButterKnife核心源码解析

作者: 走川 | 来源:发表于2017-10-17 10:27 被阅读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);
      }
      
    

    相关文章

      网友评论

          本文标题:ButterKnife核心源码解析

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