Android 淺析 ButterKnife (二) 源碼解析

Android 淺析 ButterKnife (二) 源碼解析


前言

Linus Benedict Torvalds : RTFSC – Read The Fucking Source Code

概括

這章將會(huì)根據(jù)由淺入深的過(guò)程淺析ButterKnife的注解方式。

工程結(jié)構(gòu)

  • butterknife
  • butterknife-annotations
  • butterknife-compiler

可以看到工程的目錄結(jié)構(gòu)還是很清楚的
butterknife負(fù)責(zé)工程的主要邏輯調(diào)用入口。
butterknife-annotations負(fù)責(zé)所有注解的自定義文件。
butterknife-compiler負(fù)責(zé)在編譯時(shí)解析Annotations。

butterknife-annotations

這里面的文件都是由注解組成。

/**
 * Bind a field to the view for the specified ID. The view will automatically be cast to the field
 * type.
 * <pre><code>
 * {@literal @}Bind(R.id.title) TextView title;
 * </code></pre>
 */
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
  /** View ID to which the field will be bound. */
  @IdRes int[] value();
}

例如我們最經(jīng)典的Bind方法的注解就是在這里定義的,這里通過(guò)注釋我們可以很直接知道這些注解做的是什么就不再一一解析了。

butterknife-compiler

接下來(lái)我們看編譯過(guò)程,這里是butterknife的主要部分。
所有的注解要在編譯時(shí)進(jìn)行解析,都需要自定義一個(gè)類繼承于javax.annotation.processing.AbstractProcessor,通過(guò)復(fù)寫(xiě)其中的方法來(lái)實(shí)現(xiàn)。

先來(lái)看下支持注解的類類型:

private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(//
    OnCheckedChanged.class, //
    OnClick.class, //
    OnEditorAction.class, //
    OnFocusChange.class, //
    OnItemClick.class, //
    OnItemLongClick.class, //
    OnItemSelected.class, //
    OnLongClick.class, //
    OnPageChange.class, //
    OnTextChanged.class, //
    OnTouch.class //
);

//獲取支持注解的類型
@Override public Set<String> getSupportedAnnotationTypes() {
  Set<String> types = new LinkedHashSet<>();
  types.add(Bind.class.getCanonicalName());

  for (Class<? extends Annotation> listener : LISTENERS) {
    types.add(listener.getCanonicalName());
  }

  types.add(BindArray.class.getCanonicalName());
  types.add(BindBitmap.class.getCanonicalName());
  types.add(BindBool.class.getCanonicalName());
  types.add(BindColor.class.getCanonicalName());
  types.add(BindDimen.class.getCanonicalName());
  types.add(BindDrawable.class.getCanonicalName());
  types.add(BindInt.class.getCanonicalName());
  types.add(BindString.class.getCanonicalName());
  types.add(Unbinder.class.getCanonicalName());

  return types;
}

這里我們可以看到基本都涵蓋了我們?cè)?code>butterknife-annotations自定義的文件。

接著我們來(lái)看主要的邏輯:

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
  //查找和解析注解
  Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

  //循環(huán)遍歷拿出注解中的鍵值
  for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
    TypeElement typeElement = entry.getKey();
    BindingClass bindingClass = entry.getValue();
    //寫(xiě)進(jìn)文件,生成輔助類
    bindingClass.brewJava().writeTo(filer);
  }

  return true;
}

這里我們看到主要的process函數(shù)里實(shí)現(xiàn)的就三個(gè)功能
1:查找和解析注解;
2:循環(huán)遍歷拿出注解中的鍵值;
3:寫(xiě)進(jìn)文件,生成輔助類。

我們先來(lái)看第一個(gè)功能,查找和解析注解:

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

    // Process each @Bind element.
    for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      parseBind(element, targetClassMap, erasedTargetNames);
    }
    
    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }
    
    // Process each @BindInt element.
    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceInt(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindInt.class, e);
      }
    }
    ...
}

我們拿了一個(gè)比較簡(jiǎn)單的@BindInt來(lái)分析。

private void parseResourceInt(Element element, Map<TypeElement, BindingClass> targetClassMap,
    Set<TypeElement> erasedTargetNames) {
  boolean hasError = false;
  TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
  ...
  // Verify common generated code restrictions.
  hasError |= isInaccessibleViaGeneratedCode(BindInt.class, "fields", element);
  hasError |= isBindingInWrongPackage(BindInt.class, element);

  if (hasError) {return;}

  // Assemble information on the field.
  String name = element.getSimpleName().toString();
  int id = element.getAnnotation(BindInt.class).value();

  BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
  FieldResourceBinding binding = new FieldResourceBinding(id, name, "getInteger", false);
  bindingClass.addResource(binding);

  erasedTargetNames.add(enclosingElement);
}

這里我們首先看到isInaccessibleViaGeneratedCode(),它里面判斷了三個(gè)點(diǎn):

  1. 驗(yàn)證方法修飾符不能為privatestatic。
  2. 驗(yàn)證包含類型不能為非Class
  3. 驗(yàn)證包含類的可見(jiàn)性并不是private。

接著我們來(lái)看isBindingInWrongPackage,它判斷了這個(gè)類的包名,包名不能以android.和java.開(kāi)頭,butterknife不可以在Android Framework和JDK框架內(nèi)部使用。

最后就是調(diào)用getOrCreateTargetClass函數(shù)獲取或者生成一個(gè)綁定類,并將之存入數(shù)組。

接下來(lái)我們肯定要分析最常用的注解@Bind了:

private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<TypeElement> erasedTargetNames) {
  // Verify common generated code restrictions.
  if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
      || isBindingInWrongPackage(Bind.class, element)) {
    return;
  }

  TypeMirror elementType = element.asType();
  if (elementType.getKind() == TypeKind.ARRAY) {
    parseBindMany(element, targetClassMap, erasedTargetNames);
  } else if (LIST_TYPE.equals(doubleErasure(elementType))) {
    parseBindMany(element, targetClassMap, erasedTargetNames);
  } else {
    parseBindOne(element, targetClassMap, erasedTargetNames);
  }
}

看出這里第一步也是判斷兩個(gè)isInaccessibleViaGeneratedCode()isBindingInWrongPackage的條件,接著通過(guò)parseBindMany()來(lái)解析這個(gè)注解:

  1. 驗(yàn)證這個(gè)類型是一個(gè)List還是一個(gè)array。
  2. 驗(yàn)證這個(gè)目標(biāo)類型是否繼承自View。
  3. 在作用域里組裝信息。
  4. 當(dāng)然,最后將生成的BindingClass存入數(shù)組中。

分析完findAndParseTargets我們接著回到process往下看bindingClass.brewJava()

JavaFile brewJava() {
    TypeSpec.Builder result = TypeSpec.classBuilder(className)
        .addModifiers(PUBLIC)
        .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));

    if (parentViewBinder != null) {
      result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
          TypeVariableName.get("T")));
    } else {
      result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
    }

    if (hasUnbinder()) {
      result.addType(createUnbinderClass());
    }

    result.addMethod(createBindMethod());

    return JavaFile.builder(classPackage, result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

這個(gè)函數(shù)通過(guò)將我們綁定類的信息寫(xiě)入到文件外還負(fù)責(zé)創(chuàng)建綁定和創(chuàng)建解除綁定。
在將綁定函數(shù)寫(xiě)入到文件后,整個(gè)編譯器的注解方法就結(jié)束了。接下來(lái)就到運(yùn)行時(shí)的注解過(guò)程了。

butterknife

Field and method binding for Android views. Use this class to simplify finding views and attaching listeners by binding them with annotations.
這是關(guān)于ButterKnife類的說(shuō)明,關(guān)于ButterKnife的使用基本都是通過(guò)這個(gè)主類來(lái)實(shí)現(xiàn)的。
這里面最關(guān)鍵的函數(shù)就數(shù)bind()了:

public static void bind(@NonNull Activity target) {
    bind(target, target, Finder.ACTIVITY);
}

每個(gè)bind函數(shù)實(shí)際上都是通過(guò)相同的函數(shù)不同參數(shù)實(shí)現(xiàn)。

static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
    Class<?> targetClass = target.getClass();

    ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
    viewBinder.bind(finder, target, source);
}

首先通過(guò)findViewBinderForClass生成每個(gè)類,然后再調(diào)用這些類的bind方法進(jìn)行綁定,這些類的方法都是在編譯期通過(guò)createBindMethod()方法一個(gè)個(gè)生成的。這些也就是輔助類的用處了。

總結(jié)

以上就是對(duì)ButterKnife的淺析,具體來(lái)說(shuō)就是在你寫(xiě)下注解后,ButterKnife會(huì)幫你在編譯期幫你把代碼補(bǔ)全,然后在運(yùn)行期來(lái)調(diào)用。

最后編輯于
?著作權(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)容

  • 0X0 前言 做過(guò)Android開(kāi)發(fā)的猿類很多都知道ButterKnife這么個(gè)東西。這個(gè)庫(kù)可以大大的簡(jiǎn)化我們的代...
    knightingal閱讀 792評(píng)論 1 10
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,781評(píng)論 25 709
  • 轉(zhuǎn)載于:[http://blog.csdn.net/chenkai19920410/article/details...
    雙魚(yú)大貓閱讀 590評(píng)論 0 5
  • 萍梗忽南北 相聚復(fù)相離 去年一相見(jiàn) 正值落花時(shí) 秋風(fēng)苦催歸 轉(zhuǎn)眼歲已期。
    雀替閱讀 450評(píng)論 0 0
  • 桑頭馨菲閱讀 265評(píng)論 2 1

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