ButterKnife核心源碼解析

Butterknife的源碼之前只是單純的知道大致的一個(gè)思路,沒(méi)有具體看過(guò)他的實(shí)現(xiàn)步驟。因作此文已記之。

Ox1 init :對(duì)外暴露的初始函數(shù)

 @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));

    //獲取相對(duì)應(yīng)的工具對(duì)象
    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }

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

getSupportedIptions的配置項(xiàng)在主工程的build.gradle

android {
     javaCompileOptions {
         annotationProcessorOptions {
            arguments = [debug: "2333"] //參數(shù)只能是字符串
         }
      }
  }

總結(jié)以上代碼:

  1. 獲取配置項(xiàng)
  2. 獲取相關(guān)的工具類(lèi)的實(shí)例

Ox2 process :處理注解的主體方法

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //獲取注解內(nèi)容的綁定關(guān)系
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    //循環(huán)遍歷
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

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

    return false;
  }

以上的流程有兩個(gè)疑惑點(diǎn):

  1. BindingSet:這個(gè)類(lèi)是作用是?
  2. findAndParseTargets:如何獲取到注解內(nèi)容的結(jié)果的?

我們先來(lái)看看BindingSet的核心代碼: constructor & brewJava

  //通過(guò)Budilder 構(gòu)造,對(duì)參數(shù)進(jìn)行配置
  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;
  }
  
  //根據(jù)上面的配置,生成了相對(duì)應(yīng)代碼
  JavaFile brewJava(int sdk, boolean debuggable) {
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

總結(jié)以上代碼:

BindingSet 大致的作用就是

  1. 一個(gè)文件所需要的所有綁定關(guān)系
  2. 生成的代碼的函數(shù)(通過(guò)JavaPoet

所以剩下的就在 findAndParseTargets 這個(gè)方法內(nèi)了

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

  (以下省略其他 類(lèi)似 @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;

總結(jié)以上代碼:

  1. 處理注解的信息
  2. 將結(jié)果組合成bindingMap

以上的 parseXXX 方法實(shí)現(xiàn)邏輯大體一致,因此我們只需要看其中的一個(gè)細(xì)分方法就ok了

Ox3 parseBindView :處理 @BindView注解 信息

為了讓大家更好的理解,我們先寫(xiě)一個(gè)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);
    }
  }

以下將會(huì)以上面的demo的情況來(lái)進(jìn)行講解

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

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

    //判斷 修飾符不為private 字段;注解不使用在Android、java的包內(nèi)
    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的子類(lèi);是否為接口類(lèi)型
    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;
      }
    }

    //報(bào)錯(cuò)則退出
    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);
  }
  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,881評(píng)論 25 709
  • butterknife注解框架相信很多同學(xué)都在用,但是你真的了解它的實(shí)現(xiàn)原理嗎?那么今天讓我們來(lái)看看它到底是怎么實(shí)...
    打不死的小強(qiáng)qz閱讀 723評(píng)論 0 4
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • 有些相遇是無(wú)端的,但大多數(shù)都是伴隨著緣分的,所以我依然愿意相信世間的機(jī)緣巧合都會(huì)有一個(gè)很好的過(guò)程。 ...
    菙輕舟閱讀 2,685評(píng)論 9 20
  • 一、誤操作git pull之后如何撤銷(xiāo)? 1、git reflog命令查看歷史變更記錄2、git reset --...
    Eternal丶閱讀 172評(píng)論 0 0

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