美文网首页
Butterknife框架源码浅析

Butterknife框架源码浅析

作者: 源来是你啊 | 来源:发表于2018-08-02 10:11 被阅读0次

介绍

ButterKnife是一个专注于Android系统的View注入框架,以前总是要写很多findViewById来找到View对象,有了ButterKnife可以很轻松的省去这些步骤。是大神JakeWharton的力作,目前使用很广。最重要的一点,使用ButterKnife对性能基本没有损失,因为ButterKnife用到的注解并不是在运行时反射的,而是在编译的时候生成新的class。项目集成起来也是特别方便,使用起来也是特别简单。

使用

一、首先,如果你直接在你的项目app.gradle中引用依赖,你需要:
①在项目gradle-->dependencies节点下面,引入:

classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'

②在app.gradle下面引入依赖:

compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'

完毕,然后就可以使用它。
二、第二种情况,如果你的项目是分模块的,而你的依赖恰好是在模块下引入的,则需要你:
①在相应的模块最上方添加:

apply plugin: 'com.jakewharton.butterknife'

②在此模块下添加依赖:

compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'

③在项目gradle-->dependencies节点下面,引入:

classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'

完毕。

值得注意的是:如果你使用AndroidStudio 3.0 canary 8开发,你需要在app下的build.gradle文件下的defaultConfig节点下添加:

android {
    ......
    defaultConfig {
        ......
        //添加以下代码
        javaCompileOptions {
            annotationProcessorOptions
                    {
                        includeCompileClasspath = true
                    }
        }
    }
   
}

接下来就在代码中使用Butterknife框架吧:
MainActivity

public class MainActivity extends AppCompatActivity {
     //2.注解获取对象
    @BindView(R.id.btn_c)
    Button btn_C;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1.设置view之后,绑定当前view
        ButterKnife.bind(this);
    }
    //绑定click事件
    @OnClick(R.id.btn_c)
    public void BtnClick(View view){
        Toast.makeText(MainActivity.this,"butterknife test",Toast.LENGTH_SHORT).show();
    }
}

①首先在Activity的onCreat方法中设置好View后,通过ButterKnife.bind(this)绑定View;
②然后再变量声明上面用BindView注解,传入布局文件对象的id即可获取对象;
③也可以注解点击事件,在声明函数上面使用OnClick注解,传入布局id即可,注意这里函数需要传入一个View类型的参数。

前言

不同于其他注解框架,Butterknife采用APT(Annotation Process Tool)编译时解析技术,避免程序运行时产生内耗影响程序流畅性。

普通注解框架采用运行时解析技术,其注解为RUNTIME即运行时生效,此类框架在程序运行时会产生大量的临时变量,有可能触发gc导致界面卡顿;而ButterKinfe是在编译程序时,将分析注解代码,在java文件生成class字节码文件的过程中,通过javapoet开源项目将代码写入class文件中,故而运行时不会产生影响;

实现

实现ButterKnife大致有三步:

1.通过apt技术将读取并扫描代码,获取代码元素;

2.通过代码元素,解析处理注解的代码;

3.通过javapoet填充代码,生成字节码文件;

那么看看它每一步的具体实现:

ButterKnifeProcessor类继承自AbstractProcessor类;


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

    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }

首先,ButterKnifeProcessor类init方法中,做一些初始化操作,这里有三个重要变量:

① private Elements elementUtils:这里的Elements就相当于java源文件中的所有代码元素的集合;

② private Types typeUtils:源代码当中的类型信息;

③ private Filer filer:创建文件的工具;

在init方法里面已经为这三个变量初始化完成。

接下来我们看看ButterKnifeProcessor类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);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

process方法中,首先调用了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 @BindArray element.
    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceArray(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindArray.class, e);
      }
    }

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

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

    // 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 @BindDimen element.
    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDimen(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDimen.class, e);
      }
    }

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

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

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

    // Process each @BindString element.
    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceString(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindString.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);
      }
    }

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

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

findAndParseTargets方法一开始便调用了scanForRClasses(env)函数扫描并建立代码树(ClassTree),接下来遍历代码寻找并处理注解
{(BindArray.class)
(BindBitmap.class)
(BindBool.class)
(BindColor.class)
(BindDimen.class)
(BindDrawable.class)
(BindFloat.class)
(BindInt.class)
(BindString.class)
(BindView.class)
(BindViews.class)}

就拿BindView注解举个例子,处理BindView注解时,会调用parseBindView方法对BindView注解进行处理:

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

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();

    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(id));
      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 = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    builder.addField(getId(id), new FieldViewBinding(name, type, required));

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

可以看到找到bindView所对应的id后,将其加入builderMap当中保存。

回头我们看看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);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

扫描处理完注解后,遍历BindingMap(存放的注解信息),然后通过brewJava函数(里面调用javapoet)将生成的语句通过filer写入class文件中。

至此butterknife的基本实现已经完成,基本流程如下图:

butterknife基本流程

有上图可知,在java源代码的编译过程中,apt会不断的解析java源文件声生成新的文件,然后我们的butterknife也会在编译过程中不断分析java源代码文件生成新文件,最后生成class字节码文件

相关文章

网友评论

      本文标题:Butterknife框架源码浅析

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