ButterKnife源碼分析

博文出處:ButterKnife源碼分析,歡迎大家關(guān)注我的博客寸谜,謝謝!

0x01 前言

在程序開(kāi)發(fā)的過(guò)程中属桦,總會(huì)有一些場(chǎng)景需要去寫(xiě)重復(fù)冗余的代碼熊痴。而程序員一般都是懶惰了(懶惰促使人進(jìn)步 ο ),所以就出現(xiàn)了很多可以減少重復(fù)工作的框架或者工具聂宾。比如今天要分析的主角—— ButterKnife 果善,如果你做 Android 開(kāi)發(fā)卻沒(méi)有聽(tīng)說(shuō)過(guò) ButterKnife 那就 Out 啦。ButterKnife 使用依賴注入的方式來(lái)減少程序員去編寫(xiě)一堆 findViewById 的代碼系谐,使用起來(lái)很方便巾陕。那么接下來(lái)就一步步地帶你深入理解 ButterKnife 框架。PS:最近寫(xiě)的博客篇幅都有點(diǎn)長(zhǎng)纪他,請(qǐng)耐心閱讀鄙煤!Logo 圖鎮(zhèn)樓!

butterknife_logo

0x02 ButterKnife 的使用方法

我們先講下 ButterKnife 的使用方法:

  1. app/build.gradle 中添加依賴:

     dependencies {
       compile 'com.jakewharton:butterknife:8.4.0'
       annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
     }
    
  2. Activity 中添加注解:

    public class ExampleActivity extends Activity {
      @BindView(R.id.user)
      EditText username;
      @BindView(R.id.pass)
      EditText password;
    
      @OnClick(R.id.submit)
      public void onClick(View v) {
        // TODO onClick View...
      }
    
      @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.simple_activity);
        ButterKnife.bind(this);
        // TODO Use fields...
      }
    
    }
    

使用方法非常簡(jiǎn)單茶袒,不得不贊嘆 ButterKnife 實(shí)在是太方便了梯刚。徹底跟 findViewById say goodbye 啦。但是我們也認(rèn)識(shí)到弹谁,如果一個(gè)框架使用起來(lái)越簡(jiǎn)單乾巧,那么這個(gè)框架內(nèi)部做的事情就越多句喜。所以在 ButterKnife 內(nèi)部一定做了很多事情预愤。

今天我們主要分析下 ButterKnife 的三個(gè)部分:Annotation 、ButterKnifeProcessor 和 ButterKnife 咳胃。這三個(gè)部分就把整個(gè) View 依賴注入的原理串聯(lián)起來(lái)了植康。

準(zhǔn)備好了嗎?下面我們就一探究竟展懈。(PS:本文分析的 ButterKnife 源碼為 8.4.0 版本)

0x03 Annotation

我們先來(lái)看一下其中的注解部分销睁。ButterKnife 的注解都在 butterknife-annotations 模塊下:

butterknife注解

發(fā)現(xiàn)我們平時(shí)常用的 @BindView@OnClick@OnItemClick 都在里面存崖。我們就挑 @BindView (路徑:butterknife-annotations/butterknife/BindView.java) 來(lái)看一下:

@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

注解都是用 @interface 來(lái)表示冻记。在 BindView 注解的上面還有 @Retention@Target

  • @Retention :表示注解的保留時(shí)間来惧,可選值 SOURCE(源碼時(shí))冗栗,CLASS(編譯時(shí)),RUNTIME(運(yùn)行時(shí)),默認(rèn)為 CLASS 隅居;
  • @Target :表示可以用來(lái)修飾哪些程序元素钠至,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標(biāo)注則表示可修飾所有胎源。

所以我們可知棉钧,@BindView 是用來(lái)修飾 field 的,并且保留至編譯時(shí)刻涕蚤。內(nèi)部有一個(gè)默認(rèn)屬性 value 宪卿,用來(lái)表示 View 的 id ,即平時(shí)程序中的 R.id.xxx 万栅。

0x04 ButterKnifeProcessor

如果只有 @BindView 是不行的愧捕,我們還需要去解析注解。如何去解析編譯時(shí)的注解呢申钩?我們可以創(chuàng)建一個(gè)繼承自 AbstractProcessor 的注解處理器次绘,然后實(shí)現(xiàn)相關(guān)方法。在 ButterKnifeButterKnifeProcessor (路徑:butterknife-compiler/butterknife/compiler/ButterKnifeProcessor.java) 就是用來(lái)解析這些注解的注解處理器撒遣。

init(ProcessingEnvironment env)

我們先來(lái)看看 ButterKnifeProcessor 中的 init(ProcessingEnvironment env) 方法:

@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) {
    }
}

init 中主要根據(jù) env 得到一些工具類邮偎。其中的 filter 主要是用來(lái)生成 Java 代碼,而 elementUtilstypeUtils 會(huì)在下面源碼中用到义黎。

getSupportedAnnotationTypes()

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<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    return types;
}

// 得到所有的注解
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;
}

getSupportedAnnotationTypes() 方法的作用就是返回該注解處理器所支持處理的注解集合禾进。在 getSupportedAnnotations() 中我們可以看到一些熟悉的注解,比如 @BindView 廉涕、@OnClick@OnItemClick 等泻云。

process(Set<? extends TypeElement> elements, RoundEnvironment env)

接下來(lái)就是重頭戲了,注解處理器中最重要的方法 process(Set<? extends TypeElement> elements, RoundEnvironment env) 狐蜕。process(Set<? extends TypeElement> elements, RoundEnvironment env) 的代碼看上去沒(méi)幾行宠纯,其實(shí)大部分都寫(xiě)在其他私有方法中了:

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    // 掃描所有注解,最后生成 map
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    // 遍歷 bindingMap 并且通過(guò) Filer 生成 Java 代碼
    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 true;
}

總體來(lái)看 process 方法就干了兩件事情:

  1. 掃描所有的注解层释,然后生成以 TypeElement 為 key 婆瓜,BindingSet 為 value 的 Map ;
  2. 根據(jù)生成的 Map 贡羔,遍歷后通過(guò) Filter 來(lái)生成對(duì)應(yīng)的輔助類源碼廉白。PS:ButterKnife 使用了 JavaPoet 來(lái)生成 Java 源碼。如果對(duì) JavaPoet 不太熟悉乖寒,可以先閱讀這篇文章 《javapoet——讓你從重復(fù)無(wú)聊的代碼中解放出來(lái)》 猴蹂。

我們慢慢來(lái)看,先來(lái)分析一下 findAndParseTargets(env)

// 掃描所有的ButterKnife注解楣嘁,并且生成以TypeElement為鍵磅轻,BindingSet為值的HashMap
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
    scanForRClasses(env);

    // 省略一堆解析各種注解的源碼覆获,這些源碼做的事情和下面這個(gè) for 循環(huán)一樣
    // 所以只要看這個(gè)解析 @BindView 就夠了
    ... 
    
    // Process each @BindView element.
    // 遍歷所有被 @BindView 標(biāo)注的元素
    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)看關(guān)于 BindView 的那個(gè) for 循環(huán),它會(huì)遍歷所有被 @BindView 注解的屬性瓢省,然后調(diào)用 parseBindView 方法弄息。那么我們就先看到 findAndParseTargets 的前半段,一起跟進(jìn) parseBindView 的方法中去勤婚。

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    // 得到注解 @BindView 元素所在的類元素
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    // ---------- 類型校驗(yàn)邏輯 start ---------------
    // 判斷是否被注解在屬性上摹量,如果該屬性是被 private 或者 static 修飾的,則出錯(cuò)
    // 判斷是否被注解在錯(cuò)誤的包中馒胆,若包名以“android”或者“java”開(kāi)頭缨称,則出錯(cuò)
    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();
    }
    // 判斷元素是不是View及其子類或者Interface
    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;
      }
    }
    // 如果有錯(cuò)誤 不執(zhí)行下面代碼
    if (hasError) {
      return;
    }
    //---------------- 類型校驗(yàn)邏輯 end -----------------

    // Assemble information on the field.  //得到被注解的注解值,即 R.id.xxx
    int id = element.getAnnotation(BindView.class).value();
    // 根據(jù)所在的類元素去查找 builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    // 如果相應(yīng)的 builder 已經(jīng)存在
    if (builder != null) {
      // 得到相對(duì)應(yīng)的 View 綁定的屬性名
      String existingBindingName = builder.findExistingBindingName(getId(id));
      // 若該屬性名已經(jīng)存在祝迂,則說(shuō)明之前已經(jīng)綁定過(guò)睦尽,會(huì)報(bào)錯(cuò)
      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 {
      // 如果沒(méi)有對(duì)應(yīng)的 builder ,就通過(guò) getOrCreateBindingBuilder 方法生成型雳,并且放入 builderMap 中
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
    // 得到注解名
    String name = element.getSimpleName().toString();
    // 得到注解元素的類型
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);
    // 根據(jù) id 当凡,添加相對(duì)應(yīng)的 Field 的綁定信息
    builder.addField(getId(id), new FieldViewBinding(name, type, required));

    // Add the type-erased version to the valid binding targets set.
    // 添加到待 unbind 的序列中
    erasedTargetNames.add(enclosingElement);
}

parseBindView 方法中基本上都加了注釋,在方法的開(kāi)頭會(huì)對(duì)該 element 去做校驗(yàn)纠俭。如果校驗(yàn)沒(méi)通過(guò)的話沿量,就沒(méi)有下面代碼的什么事了。若校驗(yàn)通過(guò)之后冤荆,生成該 element 所在的類元素對(duì)應(yīng)的 builder 朴则,builder 中添加相應(yīng)的 Field 綁定信息,最后添加到待 unbind 的序列中去钓简。

現(xiàn)在乌妒,我們回過(guò)頭來(lái)看看 findAndParseTargets(env) 方法的后半段:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {

    ... // 省略前半部分源碼

    // 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()) {
      // 一個(gè)個(gè)取出遍歷
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
      // 得到對(duì)應(yīng)的 key 和 value
      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();
      // 找到該類元素的父元素
      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        // 生成 BindingSet ,放入 Map 中
        bindingMap.put(type, builder.build());
      } else {
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          // 設(shè)置父元素的 BindingSet
          builder.setParent(parentBinding);
          bindingMap.put(type, builder.build());
        } else {
          // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
          // 有父元素外邓,但是父元素的 BindingSet 還沒(méi)有被 build 出來(lái)撤蚊,
          // 所以再放入 entries 中等待遍歷 
          entries.addLast(entry);
        }
      }
    }
    // 解析結(jié)果都會(huì)存放在 bindingMap 中
    return bindingMap;
}

findAndParseTargets(env) 方法的后半段中,主要就是把之前的 builderMap 轉(zhuǎn)換為了 bindingMap 并返回坐榆。

到了這里拴魄,我們把 process(Set<? extends TypeElement> elements, RoundEnvironment env) 做的第一件事情搞清楚了,下面就接著來(lái)看第二件事情了席镀。

// 遍歷 bindingMap 并且通過(guò) Filer 生成 Java 代碼
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());
  }
}

brewJava(int sdk)

從上面可以看到,遍歷了之前得到的 bindingMap 夏漱,然后利用 binding 中的信息生成相應(yīng)的 Java 源碼豪诲。所以在 binding.brewJava(sdk) 這個(gè)方法是我們重點(diǎn)關(guān)注對(duì)象。那么就進(jìn)入 BindingSet (路徑:butterknife-compiler/butterknife/compiler/BindingSet.java) 這個(gè)類中去看看吧:

JavaFile brewJava(int sdk) {
    // 生成 JavaFile挂绰,添加相應(yīng)的注釋
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
            .addFileComment("Generated code from Butter Knife. Do not modify!")
            .build();
}

brewJava(int sdk) 方法的代碼竟然這么短 O_o 屎篱,就是利用了 JavaFile.builder 生成了一個(gè) JavaFile 對(duì)象而已服赎。但是我們發(fā)現(xiàn)其中有一個(gè) createType(int sdk) 方法,隱隱約約感覺(jué)一定是這個(gè)方法在搞大事情交播。繼續(xù)跟進(jìn)去看:

private TypeSpec createType(int sdk) {
    // 生成類名為 bindingClassName 的公共類重虑,比如 MainActivity_ViewBinding
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
            .addModifiers(PUBLIC);
    // 是否修飾為 final ,默認(rèn)是 false
    if (isFinal) {
        result.addModifiers(FINAL);
    }

    if (parentBinding != null) {
        // 如果有父類的話秦士,那么要繼承父類
        result.superclass(parentBinding.bindingClassName);
    } else {
        // 如果沒(méi)有父類缺厉,那么實(shí)現(xiàn) Unbinder 接口
        result.addSuperinterface(UNBINDER);
    }

    // 增加一個(gè)變量名為target,類型為targetTypeName的成員變量
    if (hasTargetField()) {
        result.addField(targetTypeName, "target", PRIVATE);
    }
    // 如果沒(méi)有 View 綁定
    if (!constructorNeedsView()) {
        // Add a delegating constructor with a target type + view signature for reflective use.
        // 該生成的構(gòu)造方法被 @deprecated 隧土,一般作為反射使用
        result.addMethod(createBindingViewDelegateConstructor(targetTypeName));
    }
    // 生成構(gòu)造方法提针,另外 findViewById 類似的代碼都在這里生成
    // Xxxx_ViewBinding 一般都是執(zhí)行這個(gè)方法生成構(gòu)造器
    result.addMethod(createBindingConstructor(targetTypeName, sdk));

    if (hasViewBindings() || parentBinding == null) {
        //生成unBind方法
        result.addMethod(createBindingUnbindMethod(result, targetTypeName));
    }

    return result.build();
}

createType(int sdk) 方法中,基本構(gòu)建好了一個(gè)類的大概曹傀,其中對(duì)于構(gòu)造器以及類似 findViewById 的操作都是在 createBindingConstructor(targetTypeName, sdk) 中實(shí)現(xiàn):

private MethodSpec createBindingConstructor(TypeName targetType, int sdk) {
    // 創(chuàng)建構(gòu)造方法辐脖,方法修飾符為 public ,并且添加注解為UiThread
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
            .addAnnotation(UI_THREAD)
            .addModifiers(PUBLIC);
    // 如果有方法綁定皆愉,比如 @OnClick
    if (hasMethodBindings()) {
        // 如果有嗜价,那么添加 targetType 類型,final 修飾幕庐,參數(shù)名為 target 的構(gòu)造方法參數(shù)
        constructor.addParameter(targetType, "target", FINAL);
    } else {
        // 如果沒(méi)有炭剪,和上面比起來(lái)就少了一個(gè) final 修飾符
        constructor.addParameter(targetType, "target");
    }
    // 如果有注解的 View
    if (constructorNeedsView()) {
        // 那么添加 View source 參數(shù)
        constructor.addParameter(VIEW, "source");
    } else {
        // 否則添加 Context context 參數(shù)
        constructor.addParameter(CONTEXT, "context");
    }

    if (hasUnqualifiedResourceBindings()) {
        // Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
        constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
                .addMember("value", "$S", "ResourceType")
                .build());
    }

    // 如果有父類,那么會(huì)根據(jù)不同情況調(diào)用不同的 super 語(yǔ)句
    if (parentBinding != null) {
        if (parentBinding.constructorNeedsView()) {
            constructor.addStatement("super(target, source)");
        } else if (constructorNeedsView()) {
            constructor.addStatement("super(target, source.getContext())");
        } else {
            constructor.addStatement("super(target, context)");
        }
        constructor.addCode("\n");
    }
    // 如果有綁定 Field 或者方法翔脱,那么添加 this.target = target 語(yǔ)句
    if (hasTargetField()) {
        constructor.addStatement("this.target = target");
        constructor.addCode("\n");
    }
    // 如果有 View 綁定
    if (hasViewBindings()) {
        if (hasViewLocal()) {
            // Local variable in which all views will be temporarily stored.
            constructor.addStatement("$T view", VIEW);
        }
        for (ViewBinding binding : viewBindings) {
            // 為 View 綁定生成類似于 findViewById 之類的代碼
            addViewBinding(constructor, binding);
        }
        // 為 View 的集合或者數(shù)組綁定
        for (FieldCollectionViewBinding binding : collectionBindings) {
            constructor.addStatement("$L", binding.render());
        }

        if (!resourceBindings.isEmpty()) {
            constructor.addCode("\n");
        }
    }
    // 綁定 resource 資源的代碼
    if (!resourceBindings.isEmpty()) {
        if (constructorNeedsView()) {
            constructor.addStatement("$T context = source.getContext()", CONTEXT);
        }
        if (hasResourceBindingsNeedingResource(sdk)) {
            constructor.addStatement("$T res = context.getResources()", RESOURCES);
        }
        for (ResourceBinding binding : resourceBindings) {
            constructor.addStatement("$L", binding.render(sdk));
        }
    }

    return constructor.build();
}

通過(guò)上面的代碼就生成了構(gòu)造器奴拦,但是我們還是沒(méi)有看到具體 findViewById 操作的代碼。別急届吁,這些代碼都在 addViewBinding(constructor, binding) 里會(huì)看到:

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
    if (binding.isSingleFieldBinding()) {
        // Optimize the common case where there's a single binding directly to a field.
        FieldViewBinding fieldBinding = binding.getFieldBinding();
        // 注意這里直接使用了 target. 的形式错妖,所以屬性肯定是不能 private 的
        CodeBlock.Builder builder = CodeBlock.builder()
                .add("target.$L = ", fieldBinding.getName());
        // 下面都是 View 綁定的代碼
        boolean requiresCast = requiresCast(fieldBinding.getType());
        if (!requiresCast && !fieldBinding.isRequired()) {
            builder.add("source.findViewById($L)", binding.getId().code);
        } else {
            builder.add("$T.find", UTILS);
            builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
            if (requiresCast) {
                builder.add("AsType");
            }
            builder.add("(source, $L", binding.getId().code);
            if (fieldBinding.isRequired() || requiresCast) {
                builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
            }
            if (requiresCast) {
                builder.add(", $T.class", fieldBinding.getRawType());
            }
            builder.add(")");
        }
        result.addStatement("$L", builder.build());
        return;
    }

    List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
    if (requiredBindings.isEmpty()) {
        result.addStatement("view = source.findViewById($L)", binding.getId().code);
    } else if (!binding.isBoundToRoot()) {
        result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
                binding.getId().code, asHumanDescription(requiredBindings));
    }

    addFieldBinding(result, binding);
    // OnClick 等監(jiān)聽(tīng)事件綁定
    addMethodBindings(result, binding);
}

至此,整個(gè) ButterKnifeProcessor 解析注解疚沐、生成 Java 代碼的流程就走完了暂氯。我們來(lái)看看生成的代碼到底長(zhǎng)成什么樣子:

public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
  protected T target;

  private View view2131427413;

  @UiThread
  public MainActivity_ViewBinding(final T target, View source) {
    this.target = target;

    View view;
    view = Utils.findRequiredView(source, R.id.button, "field 'button' and method 'onClick'");
    target.button = Utils.castView(view, R.id.button, "field 'button'", Button.class);
    view2131427413 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onClick(p0);
      }
    });
    target.tv = Utils.findRequiredViewAsType(source, R.id.tv, "field 'textView'", TextView.class);
  }

  @Override
  @CallSuper
  public void unbind() {
    T target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.button = null;
    target.tv = null;

    view2131427413.setOnClickListener(null);
    view2131427413 = null;

    this.target = null;
  }
}

不得不贊嘆一句,JavaPoet 生成的代碼跟我們手寫(xiě)的基本上沒(méi)什么區(qū)別亮蛔。JavaPoet 實(shí)在是太強(qiáng)大了 *ο* 痴施。

0x05 ButterKnife

bind()

通過(guò)之前介紹 ButterKnife 的使用方法,我們知道 View 綁定是通過(guò)調(diào)用 ButterKnife.bind() 方法來(lái)實(shí)現(xiàn)的究流。下面我們來(lái)看看其內(nèi)部原理 (路徑:butterknife/butterknife/ButterKnife.java) :

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
  View sourceView = target.getWindow().getDecorView();
  return createBinding(target, sourceView);
}

@NonNull @UiThread
public static Unbinder bind(@NonNull View target) {
  return createBinding(target, target);
}

...

createBinding(@NonNull Object target, @NonNull View source)

發(fā)現(xiàn) bind() 方法內(nèi)都會(huì)去調(diào)用 createBinding(@NonNull Object target, @NonNull View source)

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    // 得到 target 的類名辣吃,比如 MainActivity 
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    // 找到 target 對(duì)應(yīng)的構(gòu)造器
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      // 創(chuàng)建對(duì)應(yīng)的對(duì)象
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
}

@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    // 對(duì)構(gòu)造器的查找進(jìn)行了緩存,可以直接從 Map 中獲取
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      // 得到對(duì)應(yīng)的 class 對(duì)象芬探,比如 MainActivity_ViewBinding
      Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
      //noinspection unchecked
      // 得到對(duì)應(yīng)的構(gòu)造器
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    // 進(jìn)行緩存
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}

其實(shí) createBinding(@NonNull Object target, @NonNull View source) 方法做的事情就是根據(jù) target 創(chuàng)建對(duì)應(yīng)的 targetClassName_ViewBinding 神得。在 targetClassName_ViewBinding 的構(gòu)造器中會(huì)把對(duì)應(yīng)的 View 進(jìn)行綁定(具體可以查看上面的 MainActivity_ViewBinding )。而在 findBindingConstructorForClass(Class<?> cls) 方法中也使用了 Class.forName() 反射來(lái)查找 Class 偷仿,這也是無(wú)法避免的哩簿。但是僅限于一個(gè)類的第一次查找宵蕉,之后都會(huì)從 BINDINGS 緩存中獲取。

0x06 總結(jié)

總體來(lái)說(shuō)节榜,ButterKnife 是一款十分優(yōu)秀的依賴注入框架羡玛,方便,高效宗苍,減少代碼量稼稿。最重要的是解放程序員的雙手,再也不用去寫(xiě)無(wú)聊乏味的 findViewById 了 \(╯-╰)/ 浓若。與 ButterKnife 原理相似的渺杉,還有 androidannotations 框架。感興趣的同學(xué)可以自己研究一下挪钓。那么是越,今天的 ButterKnife 解析到這里就結(jié)束了。如果對(duì)此有問(wèn)題或疑惑的同學(xué)可以留言碌上,歡迎探討倚评。

Goodbye !~~

0x07 References

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末馏予,一起剝皮案震驚了整個(gè)濱河市天梧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌霞丧,老刑警劉巖呢岗,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蛹尝,居然都是意外死亡后豫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門突那,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挫酿,“玉大人,你說(shuō)我怎么就攤上這事愕难≡绻辏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵猫缭,是天一觀的道長(zhǎng)葱弟。 經(jīng)常有香客問(wèn)我,道長(zhǎng)饵骨,這世上最難降的妖魔是什么翘悉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮居触,結(jié)果婚禮上妖混,老公的妹妹穿的比我還像新娘。我一直安慰自己轮洋,他們只是感情好制市,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著弊予,像睡著了一般祥楣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上汉柒,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天误褪,我揣著相機(jī)與錄音,去河邊找鬼碾褂。 笑死兽间,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的正塌。 我是一名探鬼主播嘀略,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼乓诽!你這毒婦竟也來(lái)了帜羊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸠天,失蹤者是張志新(化名)和其女友劉穎讼育,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體稠集,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奶段,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巍杈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忧饭。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筷畦,靈堂內(nèi)的尸體忽然破棺而出词裤,到底是詐尸還是另有隱情,我是刑警寧澤鳖宾,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布吼砂,位于F島的核電站,受9級(jí)特大地震影響鼎文,放射性物質(zhì)發(fā)生泄漏渔肩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一拇惋、第九天 我趴在偏房一處隱蔽的房頂上張望周偎。 院中可真熱鬧抹剩,春花似錦、人聲如沸蓉坎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛉艾。三九已至钳踊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勿侯,已是汗流浹背拓瞪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留助琐,地道東北人祭埂。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像弓柱,于是被迫代替她去往敵國(guó)和親沟堡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容