帶你讀懂 ButterKnife 的源碼

爲何要寫這一系列的博客呢?java

由於在 Android 開發的過程當中, 泛型,反射,註解這些知識進場會用到,幾乎全部的框架至少都會用到上面的一兩種知識,如 Gson 就用到泛型,反射,註解,Retrofit 也用到泛型,反射,註解 。學好這些知識對咱們進階很是重要,尤爲是閱讀開源框架源碼或者本身開發開源框架。android

java Type 詳解git

java 反射機制詳解程序員

註解使用入門(一)github

Android 自定義編譯時註解1 - 簡單的例子面試

Android 編譯時註解 —— 語法詳解緩存

帶你讀懂 ButterKnife 的源碼bash

前言

ButterKnife 這個開源庫火了有一段時間了,剛開始它的實現原理是使用反射實現的,性能較差。再後面的 版本中逐漸使用註解+放射實現,性能提升了很多。微信

ButterKnife 是基於編譯時的框架,它可以幫助咱們減去每次寫 FindViewById 的麻煩,截止到 2017.5.1 ,在 github 上面的 start 已經超過 15000.app

本篇博客要分析的 ButterKnife 的源碼主要包括如下三個部分,版本號是8.5.1

  • butterknife-annotations
  • butterknife-compiler
  • butterknife

其中 butterknife-annotations 庫主要用來存放自定義註解;butterknife-compiler 主要是用來掃描哪些地方使用到咱們的自定義註解,並進行相應的處理,生成模板代碼等;butterknife 主要是用來注入咱們的代碼的。

咱們先來先一下要怎樣使用 butterknife:

ButterKnife 的基本使用

在 moudle 的 build.gradle 增長依賴

dependencies {
  compile 'com.jakewharton:butterknife:8.5.1'
  annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}
複製代碼
public class SimpleActivity extends Activity {
  private static final ButterKnife.Action<View> ALPHA_FADE = new ButterKnife.Action<View>() {
    @Override public void apply(@NonNull View view, int index) {
      AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
      alphaAnimation.setFillBefore(true);
      alphaAnimation.setDuration(500);
      alphaAnimation.setStartOffset(index * 100);
      view.startAnimation(alphaAnimation);
    }
  };

  @BindView(R2.id.title) TextView title;
  @BindView(R2.id.subtitle) TextView subtitle;
  @BindView(R2.id.hello) Button hello;
  @BindView(R2.id.list_of_things) ListView listOfThings;
  @BindView(R2.id.footer) TextView footer;

  @BindViews({ R2.id.title, R2.id.subtitle, R2.id.hello }) List<View> headerViews;

  private SimpleAdapter adapter;

  @OnClick(R2.id.hello) void sayHello() {
    Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
    ButterKnife.apply(headerViews, ALPHA_FADE);
  }

  @OnLongClick(R2.id.hello) boolean sayGetOffMe() {
    Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show();
    return true;
  }

  @OnItemClick(R2.id.list_of_things) void onItemClick(int position) {
    Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
  }

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);

    // Contrived code to use the bound fields.
    title.setText("Butter Knife");
    subtitle.setText("Field and method binding for Android views.");
    footer.setText("by Jake Wharton");
    hello.setText("Say Hello");

    adapter = new SimpleAdapter(this);
    listOfThings.setAdapter(adapter);
  }
}
複製代碼

調用 gradle build 命令,咱們在相應的目錄下將能夠看到生成相似這樣的代碼。

public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
  protected T target;

  private View view2130968578;

  private View view2130968579;

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

    View view;
    target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
    view2130968578 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
    view2130968579 = view;
    ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
  }

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

    target.title = null;
    target.subtitle = null;
    target.hello = null;
    target.listOfThings = null;
    target.footer = null;
    target.headerViews = null;

    view2130968578.setOnClickListener(null);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;

    this.target = null;
  }
}
複製代碼

ButterKnife 的執行流程

總的來講,大概能夠分爲如下幾步:

  • 在編譯的時候掃描註解,並作相應的處理,生成 java 代碼,生成 Java 代碼是調用 javapoet 庫生成的。
  • 當咱們調用 ButterKnife.bind(this); 方法的時候,他會根據類的全限定類型,找到相應的代碼,並執行。完成 findViewById 和 setOnClick ,setOnLongClick 等操做。

第一步:在編譯的時候掃描註解,並作相應的處理,生成 java 代碼。這一步,能夠拆分爲幾個小步驟:

  • 定義咱們的註解,聲明咱們的註解是否保存到 java doc 中,能夠做用於哪些區域(Filed ,Class等),以及是源碼時註解,編譯時註解仍是運行時註解等)
  • 繼承 AbstractProcessor,表示支持哪些類型的註解,支持哪些版本,
  • 重寫 process 方法,處理相關的註解,存進 Map 集合中
  • 根據掃描到的註解信息(即 Map 集合),調用 javapoet 庫生成 Java 代碼。

butterknife-annotations 講解

咱們知道 ButterKnife 自定義不少的註解,有 BindArray,BindBitmap,BindColor,BindView 等,這裏咱們以 BindView 爲例子講解就 OK 了,其餘的也是基本相似的,這裏就再也不講解了。

//編譯時註解
@Retention(CLASS)
//成員變量, (includes enum constants) 
@Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}
複製代碼

Processor 解析器說明

咱們先來看一些基本方法:在 init 方法裏面獲得一些輔助工具類,這樣有一個好處,確保工具類是單例的,由於 init 方法只會在初始化的時候調用。若是對註解還不瞭解的話,建議先閱讀這一篇博客,Android 編譯時註解 —— 語法詳解

public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    ---
   
    //輔助工具類
    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
   
   ---
}


複製代碼

接着重寫 getSupportedAnnotationTypes 方法,返回咱們支持的註解類型。

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


複製代碼

接下來來看咱們的重點, process 方法。所作的工做大概就是拿到咱們全部的註解信息,存進 map 集合,遍歷 map 集合,作相應的 處理,生成 java 代碼。

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //  拿到全部的註解信息,TypeElement 做爲 key,BindingSet 做爲 value
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    // 遍歷 map 裏面的全部信息,並生成 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 false;
}

複製代碼

這裏咱們進入 findAndParseTargets 方法,看裏面究竟是怎樣將註解信息存進 map 集合的?

findAndParseTargets 方法裏面 針對每個自定義註解(BindArray,BindBitmap,BindColor,BindView) 等都作了處理,這裏咱們重點關注 @BindView 的處理便可。其餘註解的處理思想也是同樣的。

咱們先來看一下 findAndParseTargets 方法的前半部分,遍歷 env.getElementsAnnotatedWith(BindView.class) 集合,並調用 parseBindView 方法去轉化。

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    scanForRClasses(env);

   
    // 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); } } --- // 後半部分,待會再講 } 複製代碼

能夠看到牽絆部分的主要邏輯在 parseBindView 方法裏面,主要作了如下幾步操做:

  • 判斷被註解 @BindView 修飾的成員變量是否是合法的,private 或者 static 修飾的,則出錯。
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
                           Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // 判斷是否被註解在屬性上,若是該屬性是被 private 或者 static 修飾的,則出錯
    // 判斷是否被註解在錯誤的包中,若包名以「android」或者「java」開頭,則出錯
    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();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    // 判斷元素是否是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, qualifiedName, simpleName);
        } else {
            error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
                    BindView.class.getSimpleName(), qualifiedName, simpleName);
            hasError = true;
        }
    }
    // 若是有錯誤,直接返回
    if (hasError) {
        return;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();
    // 根據所在的類元素去查找 builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    // 若是相應的 builder 已經存在
    if (builder != null) {
        // 驗證 ID 是否已經被綁定
        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,就須要從新生成,並別存放到  builderMap 中
        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);
}


複製代碼

parseBindView 方法分析完畢以後,咱們在回過頭來看一下 findAndParseTargets 方法的後半部分,主要作的工做是對 bindingMap 進行重排序。

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()) {
        Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

        TypeElement type = entry.getKey();
        BindingSet.Builder builder = entry.getValue();
        //獲取 type 的父類的 TypeElement
        TypeElement parentType = findParentType(type, erasedTargetNames);
        // 爲空,存進 map
        if (parentType == null) {
            bindingMap.put(type, builder.build());
        } else {
             // 獲取 parentType 的 BindingSet
            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; } 複製代碼

到這裏爲止,咱們已經分析完 ButterKnifeProcessor 是怎樣處理註解的相關知識,並存進 map 集合中的,下面咱們回到 process 方法,看一下是怎樣生成 java 模板代碼的

public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //  拿到全部的註解信息,TypeElement 做爲 key,BindingSet 做爲 value
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    // 遍歷 map 裏面的全部信息,並生成 java 代碼
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingSet binding = entry.getValue();
         // 生成 javaFile 對象
        JavaFile javaFile = binding.brewJava(sdk);
        try {
             //  生成 java 模板代碼              
            javaFile.writeTo(filer);
        } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e
                    .getMessage());
        }
    }

    return false;
}

複製代碼

生成代碼的核心代碼只有這幾行

// 生成 javaFile 對象
JavaFile javaFile = binding.brewJava(sdk);
try {
     //  生成 java 模板代碼
    javaFile.writeTo(filer);
} catch (IOException e) {
    error(typeElement, "Unable to write binding for type %s: %s", typeElement, e
            .getMessage());
}


複製代碼

跟蹤進去,發現是調用 square 公司開源的庫 javapoet 開生成代碼的。關於 javaPoet 的使用能夠參考官網地址

JavaFile brewJava(int sdk) {
  return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
      .addFileComment("Generated code from Butter Knife. Do not modify!")
      .build();
}

private TypeSpec createType(int sdk) {
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
            .addModifiers(PUBLIC);
    if (isFinal) {
        result.addModifiers(FINAL);
    }

    if (parentBinding != null) {
        result.superclass(parentBinding.bindingClassName);
    } else {
        result.addSuperinterface(UNBINDER);
    }

    if (hasTargetField()) {
        result.addField(targetTypeName, "target", PRIVATE);
    }
    // 若是是 View 或者是 View 的子類的話,添加構造方法
    if (isView) {
        result.addMethod(createBindingConstructorForView());
    } else if (isActivity) { // 若是是 Activity 或者是 Activity 的子類的話,添加構造方法
        result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {  // 若是是 Dialog 或者是 Dialog 的子類的話,添加構造方法
        result.addMethod(createBindingConstructorForDialog());
    }
    //  若是構造方法不須要 View 參數,添加 須要 View 參數的構造方法
    if (!constructorNeedsView()) {
        // Add a delegating constructor with a target type + view signature for reflective use.
        result.addMethod(createBindingViewDelegateConstructor());
    }
    result.addMethod(createBindingConstructor(sdk));

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

    return result.build();
}




複製代碼

接着咱們一塊兒來看一下 createBindingConstructor(sdk) 方法,大概作的事情就是

  • 判斷是否有設置監聽,若是有監聽,將 View 設置爲 final
  • 遍歷 viewBindings ,調用 addViewBinding 生成 findViewById 形式的代碼。
private MethodSpec createBindingConstructor(int sdk) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
            .addAnnotation(UI_THREAD)
            .addModifiers(PUBLIC);
    // 若是有方法綁定,好比 @onClick,那麼增長一個 targetTypeName 類型  的方法參數 target,而且是 final 類型的
    if (hasMethodBindings()) {
        constructor.addParameter(targetTypeName, "target", FINAL);
    } else { // 若是沒有 ,不是 final 類型的
        constructor.addParameter(targetTypeName, "target");
    }
    //若是有註解的 View,那麼添加 VIEW 類型 source 參數
    if (constructorNeedsView()) {
        constructor.addParameter(VIEW, "source");
    } else {
        //  添加 Context 類型的 context 參數
        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());
    }
    // 若是 @OnTouch 綁定 View,添加 @SuppressLint("ClickableViewAccessibility")
    if (hasOnTouchMethodBindings()) {
        constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
                .addMember("value", "$S", "ClickableViewAccessibility")
                .build());
    }
    // 若是 parentBinding 不爲空,調用父類 的構造方法
    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");
    }
    //  添加成員變量
    if (hasTargetField()) {
        constructor.addStatement("this.target = target");
        constructor.addCode("\n");
    }

    if (hasViewBindings()) {
        if (hasViewLocal()) {
            // Local variable in which all views will be temporarily stored.
            constructor.addStatement("$T view", VIEW);
        }
        //   遍歷  viewBindings,生成  source.findViewById($L) 代碼
        for (ViewBinding binding : viewBindings) {
            addViewBinding(constructor, binding);
        }
        for (FieldCollectionViewBinding binding : collectionBindings) {
            constructor.addStatement("$L", binding.render());
        }

        if (!resourceBindings.isEmpty()) {
            constructor.addCode("\n");
        }
    }

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


複製代碼

下面咱們一塊兒來看一下 addViewBinding 方法是怎樣生成代碼的。

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()); 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; } 複製代碼

ButterKnife 是怎樣實現代碼注入的

使用過 ButterKnife 得人基本都知道,咱們是經過 bind 方法來實現注入的,即自動幫咱們 findViewById ,解放咱們的雙手,提升工做效率。下面咱們一塊兒來看一下 bind 方法是怎樣實現注入的。

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

複製代碼

能夠看到 bind 方法很簡單,邏輯基本都交給 createBinding 方法去完成。咱們一塊兒進入 createBinding 方法來看一下到底作了什麼。

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    // 從 Class 中查找 constructor
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

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

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
        // 反射實例化構造方法
        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);
    }
}


複製代碼

其實 createBinding 來講,主要作了這幾件事情

  • 傳入 class ,經過 findBindingConstructorForClass 方法來實例化 constructor
  • 利用反射來初始化 constructor 對象
  • 初始化 constructor 失敗會拋出異常

下面咱們一塊兒來看一下 findBindingConstructorForClass 方法是怎樣實現的。

private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //  讀取緩存,若是不爲空,直接返回
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
        if (debug) Log.d(TAG, "HIT: Cached in binding map.");
        return bindingCtor;
    }
    // 若是是 android ,java 原生的文件,不處理
    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 {
        Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
        //noinspection unchecked
        // 在原來所在的類查找
        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);
    }
    // 存進 LinkedHashMap 緩存
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}


複製代碼

它的實現思想是這樣的:

  • 讀取緩存,若緩存命中,直接返回,這樣有利於提升效率。從代碼中能夠看到,緩存是經過存進 map 集合實現的。
  • 是不是咱們目標文件,是的話,進行處理,不是的話,直接返回,並打印相應的日誌
  • 利用類加載器加載咱們本身生成的 class 文件,並獲取其構造方法,獲取到,直接返回。獲取不到,會拋出異常,在異常的處理中,咱們再從當前 class 文件的父類去查找。並把結果存進 map 集合中,作緩存處理。

咱們對 ButterKnife 的分析到此爲止。


題外話

這篇博客主要是分析了 ButterKnife 的主要原理實現,對 ButterKnife 裏面的一些實現細節並未詳細分析。不過對咱們讀懂代碼已經足夠了。下一個系列,主要講解 CoordinatorLayout 的實現原理及怎樣自定義 CoordinatorLayout 的 behavior 實現仿新浪微博發現頁面的效果,敬請期待。


相關博客推薦

java Type 詳解

java 反射機制詳解

註解使用入門(一)

Android 自定義編譯時註解1 - 簡單的例子

Android 編譯時註解 —— 語法詳解

帶你讀懂 ButterKnife 的源碼

掃一掃,歡迎關注個人微信公衆號 stormjun94(徐公碼字), 目前是一名程序員,不只分享 Android開發相關知識,同時還分享技術人成長曆程,包括我的總結,職場經驗,面試經驗等,但願能讓你少走一點彎路。

相關文章
相關標籤/搜索