编程语言
首页 > 编程语言> > ButterKnife 8.4.0 源码分析系列文章(一)(二)(三)(四)(五)

ButterKnife 8.4.0 源码分析系列文章(一)(二)(三)(四)(五)

作者:互联网

ButterKnife 8.4.0 源码分析(一)

前言

本文是根据ButterKnife的历史版本 8.4.0进行分析的。

ButterKnife 用到了编译时技术(APT Annotation Processing Tool),再讲解源码之前我们先看看这部分内容。

编译时技术(APT技术)

讲解编译时技术前,我们需要先了解下代码的生命周期。

在这里插入图片描述

如图所示,代码的生命周期分为源码期、编译期、运行期。

APT,就是Annotation Processing Tool 的简称,就是可以在代码编译期间对注解进行处理,并且生成java文件,减少手动的代码输入。比如在ButterKnife中,我们通过自定义注解处理器来对@BindView注解以及注解的的元素进行处理,最终生成XXXActivity$$ViewBinder.class文件,来加少我们使用者findViewById等手动代码的输入。

Java注解处理器

注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以对自定义注解,并注册相应的注解处理器。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这具体的含义什么呢?你可以生成Java代码!这些生成的Java代码是在生成的.java文件中,但你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

我们首先看一下处理器的API。每一个处理器都是继承于AbstractProcessor,如下所示:

public class MyProcessor extends AbstractProcessor {
    private Filer mFiler; //文件相关的辅助类
    private Elements mElementUtils; //元素相关的辅助类
    private Messager mMessager; //日志相关的辅助类

    //用来指定你使用的 java 版本。通常你应该返回 SourceVersion.latestSupported()
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //会被处理器调用,可以在这里获取Filer,Elements,Messager等辅助类
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
    }


    //这个方法返回stirng类型的set集合,集合里包含了你需要处理的注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add("com.example.MyAnnotation");
        return annotataions;
    }


   //核心方法,这个一般的流程就是先扫描查找注解,再生成 java 文件
   //这2个步骤设计的知识点细节很多。

    @Override
    public boolean process(Set<? extends TypeElement> annoations,
            RoundEnvironment env) {
        return false;
    }
}

注册你的处理器

要像jvm调用你写的处理器,你必须先注册,让他知道。怎么让它知道呢,其实很简单,google 为我们提供了一个库,简单的一个注解就可以。

首先是依赖

compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
  //...省略非关键代码
}

接着只需要在你的注解处理器上加上@AutoService(Processor.class)即可

基本概念

到此为止,一个基本的注解处理器就写好了。

下一章节我们再看看ButterKnife的内部实现吧。

ButterKnife 8.4.0 源码分析(二)

ButterKnife 中@BindView注解处理流程分析

原理图

以下是整个库的处理流程,大家可以看完流程分析后再回过头来重看一遍。

在这里插入图片描述

ButterKnife的核心则是ButterKnifeProcessor 这个类。

(1)init 方法,这个主要是获取一些辅助类
    private Filer mFiler; //文件相关的辅助类
    private Elements mElementUtils; //元素相关的辅助类
    private Messager mMessager; //日志相关的辅助类

  @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }
(2)getSupportedSourceVersion()方法就是默认的,获取你该处理器使用的java版本
 @Override public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }
(3)getSupportedAnnotations方法已经介绍过了,阐述注解处理器应该处理那些注解。以下是ButterKnifeProcessor所处理的注解。
  private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

    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(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }
(4)接下来就是process方法,这个方法是核心方法。
@Override 
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
 //1.查找所有的注解信息,并形成BindingClass(是什么 后面会讲) 保存到 map中
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

//2.遍历步骤1的map 的生成.java文件(也就是上文的  类名_ViewBinding  的java文件)
  for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();
      JavaFile javaFile = bindingClass.brewJava();
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return true;
  }

每个注解的查找与解析-findAndParseTargets

private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    scanForRClasses(env);
    // 处理每个被 @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, targetClassMap, erasedTargetNames);
     } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
   //....


      // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }
   
    return targetClassMap;

}

首先我们先看一下参数 RoundEnvironment ,一个可以在处理器处理该处理器 用来查询注解信息的工具,当然包含你在getSupportedAnnotationTypes注册的所有注解。

我们这里只分析一个具有代表性的BindView注解,其它的都是一样的,连代码都一毛一样。

接着我们继续看 parseBindView();这个方法

  private void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

     //1.检查用户使用的合法性
    // Start by verifying common generated code restrictions.
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);

    // 鉴定 element的类型是不是继承自View
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    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, enclosingElement.getQualifiedName(),
            element.getSimpleName());
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
            element.getSimpleName());
        hasError = true;
      }
    }

    // 不合法的直接返回
    if (hasError) {
      return;
    }


    //2.获取变量上的BindView 注解的id 值
    int id = element.getAnnotation(BindView.class).value();

    // 3.获取 BindingClass,有缓存机制, 没有则创建,下文会仔细分析

    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass != null) {
      ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));
      if (viewBindings != null && viewBindings.getFieldBinding() != null) {
        FieldViewBinding existingBinding = viewBindings.getFieldBinding();
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBinding.getName(),
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    }
   //4 生成FieldViewBinding 实体 

    String name = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    FieldViewBinding binding = new FieldViewBinding(name, type, required);
     //5 将FieldViewBinding 对象 加入到 bindingClass 成员变量的集合中 (其实这里也是通过Id先得到ViewBindings,再把FieldViewBinding加入到bindingClass的ViewBindings中 点击去看一遍可以知道)
    bindingClass.addField(getId(id), binding);

    // Add the type-erased version to the valid binding         targets set.
    erasedTargetNames.add(enclosingElement);
  }

element.getEnclosingElement();是什么呢?是父节点。就是上面我们说的。其实就是获得@BindView注解所在的类。

基本的步骤就是上面的5步

1.检查用户使用的合法性

 boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);
 private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
      String targetThing, Element element) {
    boolean hasError = false;
    // 得到父节点 就是@BindView注解所在的类
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

 //判断修饰符,如果包含private or static 就会抛出异常。
    // Verify method modifiers.
    Set<Modifier> modifiers = element.getModifiers();
    if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
      error(element, "@%s %s must not be private or static. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

        //判断父节点是否是类类型的,不是的话就会抛出异常
        //也就是说BindView 的使用必须在一个类里
    // Verify containing type.
    if (enclosingElement.getKind() != CLASS) {
      error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

     //判断父节点如果是private 类,则抛出异常
    // Verify containing class visibility is not private.
    if (enclosingElement.getModifiers().contains(PRIVATE)) {
      error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    return hasError;
  }

上面的代码里遇见注释了,这里说一下也就是我们在使用bindview注解的时候不能用使用

类不能是private修饰 ,可以是默认的或者public 
  //in adapter
   private  static final class ViewHolder {
   //....
   }
   //成员变量不能是private修饰 ,可以是默认的或者public 
   @BindView(R.id.word)
  private  TextView word; 

接下来还有一个方法isBindingInWrongPackage。
这个看名字也才出来个大概 就是不能在android ,java这种源码的sdk中使用。如果你的包名是以android或者java开头就会抛出异常。

 private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
      Element element) {
      // 得到父节点
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    String qualifiedName = enclosingElement.getQualifiedName().toString();

    if (qualifiedName.startsWith("android.")) {
      error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }
    if (qualifiedName.startsWith("java.")) {
      error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }

    return false;
  } 

2.获取变量上的BindView 注解的id 值

比如

@BindView(R.id.tvTitle)
TextView title;

这里获取到id值就是R.id.tvTitle。

好了,,这一章节到此结束啦。接下来我们再看接下来的3,4,5三步。

剩下的(三)(四)(五)章节的内容会在我的github主页上放置链接 checkout it out:
https://github.com/tomridder/ButterKnife-8.4.0-

标签:8.4,private,element,ButterKnife,源码,env,注解,class,BindView
来源: https://blog.csdn.net/qq_24306715/article/details/122785524