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

ButterKnife 8.4.0 源碼分析(一)

前言

本文是根據(jù)ButterKnife的歷史版本 8.4.0進(jìn)行分析的。

ButterKnife 用到了編譯時(shí)技術(shù)(APT Annotation Processing Tool),再講解源碼之前我們先看看這部分內(nèi)容。

編譯時(shí)技術(shù)(APT技術(shù))

講解編譯時(shí)技術(shù)前,我們需要先了解下代碼的生命周期。

BK源碼生命周期.png

如圖所示,代碼的生命周期分為源碼期、編譯期、運(yùn)行期。

  • 源碼期 當(dāng)我們在寫以.java結(jié)尾的文件的時(shí)期。
  • 編譯期 則指的是是指把源碼交給編譯器編譯成計(jì)算機(jī)可以執(zhí)行的文件的過程。在Java中也就是把Java代碼編成class文件的過程.。
  • 運(yùn)行期 則指的是java代碼的運(yùn)行過程。如我們的app運(yùn)行在手機(jī)中的時(shí)期。

APT,就是Annotation Processing Tool 的簡稱,就是可以在代碼編譯期間對注解進(jìn)行處理,并且生成java文件,減少手動(dòng)的代碼輸入。比如在ButterKnife中,我們通過自定義注解處理器來對@BindView注解以及注解的的元素進(jìn)行處理,最終生成XXXActivity$$ViewBinder.class文件,來加少我們使用者findViewById等手動(dòng)代碼的輸入。

Java注解處理器

注解處理器(Annotation Processor)是javac的一個(gè)工具,它用來在編譯時(shí)掃描和處理注解(Annotation)。你可以對自定義注解,并注冊相應(yīng)的注解處理器。一個(gè)注解的注解處理器,以Java代碼(或者編譯過的字節(jié)碼)作為輸入,生成文件(通常是.java文件)作為輸出。這具體的含義什么呢?你可以生成Java代碼!這些生成的Java代碼是在生成的.java文件中,但你不能修改已經(jīng)存在的Java類,例如向已有的類中添加方法。這些生成的Java文件,會(huì)同其他普通的手動(dòng)編寫的Java源代碼一樣被javac編譯。

我們首先看一下處理器的API。每一個(gè)處理器都是繼承于AbstractProcessor,如下所示:

public class MyProcessor extends AbstractProcessor {
    private Filer mFiler; //文件相關(guān)的輔助類
    private Elements mElementUtils; //元素相關(guān)的輔助類
    private Messager mMessager; //日志相關(guān)的輔助類

    //用來指定你使用的 java 版本。通常你應(yīng)該返回 SourceVersion.latestSupported()
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //會(huì)被處理器調(diào)用,可以在這里獲取Filer,Elements,Messager等輔助類
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
    }


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


   //核心方法,這個(gè)一般的流程就是先掃描查找注解,再生成 java 文件
   //這2個(gè)步驟設(shè)計(jì)的知識點(diǎn)細(xì)節(jié)很多。

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

注冊你的處理器

要像jvm調(diào)用你寫的處理器,你必須先注冊,讓他知道。怎么讓它知道呢,其實(shí)很簡單,google 為我們提供了一個(gè)庫,簡單的一個(gè)注解就可以。

首先是依賴

compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
  //...省略非關(guān)鍵代碼
}

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

基本概念

  • Elements:一個(gè)用來處理Element的工具類

    我們看一下Element這個(gè)類。在注解處理器中,我們掃描 java 源文件,源代碼中的每一部分都是Element的一個(gè)特定類型。換句話說:Element代表程序中的元素,比如說 包,類,方法。每一個(gè)元素代表一個(gè)靜態(tài)的,語言級別的結(jié)構(gòu).

    比如:

    package com.example;    // PackageElement 包元素
    
    public class Foo {        // TypeElement 類元素
    
        private int a;      // VariableElement 變量元素
        private Foo other;  // VariableElement 變量元素
    
        public Foo () {}    // ExecuteableElement 方法元素
    
        public void setA (  // ExecuteableElement 方法元素
                         int newA   // VariableElement 變量元素
                         ) {}
    }
    

    一句話概括:Element代表源代碼,TypeElement代表源代碼中的元素類型,例如類。然后,TypeElement并不包含類的相關(guān)信息。你可以從TypeElement獲取類的名稱,但你不能獲取類的信息,比如說父類。這些信息可以通過TypeMirror獲取。你可以通過調(diào)用element.asType()來獲取一個(gè)Element的TypeMirror。

  • Types:一個(gè)用來處理TypeMirror的工具類

  • Filer:你可以使用這個(gè)類來創(chuàng)建.java文件

  • Messager : 日志相關(guān)的輔助類

到此為止,一個(gè)基本的注解處理器就寫好了。

下一章節(jié)我們再看看ButterKnife的內(nèi)部實(shí)現(xiàn)吧。

ButterKnife 8.4.0 源碼分析(二)

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

原理圖

以下是整個(gè)庫的處理流程,大家可以看完流程分析后再回過頭來重看一遍。

BK架構(gòu)圖.png

ButterKnife的核心則是ButterKnifeProcessor 這個(gè)類。

(1)init 方法,這個(gè)主要是獲取一些輔助類
    private Filer mFiler; //文件相關(guān)的輔助類
    private Elements mElementUtils; //元素相關(guān)的輔助類
    private Messager mMessager; //日志相關(guān)的輔助類

  @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()方法就是默認(rèn)的,獲取你該處理器使用的java版本
 @Override public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }
(3)getSupportedAnnotations方法已經(jīng)介紹過了,闡述注解處理器應(yīng)該處理那些注解。以下是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方法,這個(gè)方法是核心方法。
@Override 
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
 //1.查找所有的注解信息,并形成BindingClass(是什么 后面會(huì)講) 保存到 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;
  }

每個(gè)注解的查找與解析-findAndParseTargets

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

    scanForRClasses(env);
    // 處理每個(gè)被 @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;

}

首先我們先看一下參數(shù) RoundEnvironment ,一個(gè)可以在處理器處理該處理器 用來查詢注解信息的工具,當(dāng)然包含你在getSupportedAnnotationTypes注冊的所有注解。

我們這里只分析一個(gè)具有代表性的BindView注解,其它的都是一樣的,連代碼都一毛一樣。

接著我們繼續(xù)看 parseBindView();這個(gè)方法

  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,有緩存機(jī)制, 沒有則創(chuàng)建,下文會(huì)仔細(xì)分析

    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 實(shí)體 

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

    FieldViewBinding binding = new FieldViewBinding(name, type, required);
     //5 將FieldViewBinding 對象 加入到 bindingClass 成員變量的集合中 (其實(shí)這里也是通過Id先得到ViewBindings,再把FieldViewBinding加入到bindingClass的ViewBindings中 點(diǎn)擊去看一遍可以知道)
    bindingClass.addField(getId(id), binding);

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

element.getEnclosingElement();是什么呢?是父節(jié)點(diǎn)。就是上面我們說的。其實(shí)就是獲得@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;
    // 得到父節(jié)點(diǎn) 就是@BindView注解所在的類
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

 //判斷修飾符,如果包含private or static 就會(huì)拋出異常。
    // 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;
    }

        //判斷父節(jié)點(diǎn)是否是類類型的,不是的話就會(huì)拋出異常
        //也就是說BindView 的使用必須在一個(gè)類里
    // 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;
    }

     //判斷父節(jié)點(diǎn)如果是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注解的時(shí)候不能用使用

類不能是private修飾 ,可以是默認(rèn)的或者public 
  //in adapter
   private  static final class ViewHolder {
   //....
   }
   //成員變量不能是private修飾 ,可以是默認(rèn)的或者public 
   @BindView(R.id.word)
  private  TextView word; 

接下來還有一個(gè)方法isBindingInWrongPackage。
這個(gè)看名字也才出來個(gè)大概 就是不能在android ,java這種源碼的sdk中使用。如果你的包名是以android或者java開頭就會(huì)拋出異常。

 private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
      Element element) {
      // 得到父節(jié)點(diǎn)
    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。

好了,,這一章節(jié)到此結(jié)束啦。接下來我們再看接下來的3,4,5三步。
剩下的(三)(四)(五)章節(jié)的內(nèi)容會(huì)在我的github主頁上放置鏈接 checkout it out:
https://github.com/tomridder/ButterKnife-8.4.0-

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容