ButterKnife 原理解析

ButterKnife 算是一款知名老牌 Android 開發框架了,經過註解綁定視圖,避免了 findViewById() 的操做,廣受好評!因爲它是在編譯時對註解進行解析完成相關代碼的生成,因此在項目編譯時會略耗時,但不會影響運行時的性能。接下來讓咱們從使用到原理一步步深刻了解這把黃油刀的故事!java

如下內容基於 butterknife:8.8.1 版本,主要包括以下幾個方面的內容:android

  • 簡單使用
  • 原理分析
  • 註解處理器
  • JavaPoet

1、簡單使用

首先編寫一個 ButterKnife 簡單使用的例子,方便後續的分析。先在 app的 build.gradle 中加入以下配置,完成 ButterKnife 引入:git

dependencies {
    ......
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
複製代碼

接下來在 Activity 中使用,界面上一個TextView一個Button,很簡單就不解釋了:github

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv_title)
    TextView title;

    @OnClick(R.id.bt_submit)
    public void submit() {
        title.setText("hello world");
    }

    private Unbinder unbinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        unbinder = ButterKnife.bind(this);
    }

    @Override
    protected void onDestroy() {
        unbinder.unbind();
        super.onDestroy();
    }
}
複製代碼

2、原理分析

最後編譯一下項目。直覺告訴咱們應該從ButterKnife.bind(this)開始分析,由於它像是 ButterKnife 和 Activity 創建綁定關係的過程,看具體的代碼:編程

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

sourceView表明當前界面的頂級父 View,是一個FrameLayout,繼續看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());
    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);
    } 
    // 省略了相關異常處理代碼
  }
複製代碼

首先獲得要綁定的 Activity 對應的 Class,而後用根據 Class 獲得一個繼承了UnbinderConstructor,最後經過反射constructor.newInstance(target, source)獲得Unbinder子類的一個實例,到此ButterKnife.bind(this)操做結束。這裏咱們重點關注findBindingConstructorForClass()方法是如何獲得constructor實例的:緩存

@Nullable @CheckResult @UiThread
  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;
    }
    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);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
複製代碼

總體的流程是先檢查BINDINGS是否存在 Class 對應的 Constructor,若是存在則直接返回,不然去構造對應的 Constructor。其中BINDINGS是一個LinkedHashMapMap<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>(),緩存了對應的 Class 和 Constructor 以提升效率! 接下來看當不存在對應 Constructor 時如何構造一個新的,首先若是clsName是系統相關的,則直接返回 null,不然先建立一個新的 Class:app

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
複製代碼

這裏新的 Class 的 name 是 com.shh.sometest.MainActivity_ViewBinding,最後用新的 Class 建立一個 繼承了Unbinder的 Constructor,並添加到BINDINGS框架

bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
BINDINGS.put(cls, bindingCtor);
複製代碼

因此最終bind()方法返回的是MainActivity_ViewBinding類的實例。既然能夠返回MainActivity_ViewBinding的實例,那MainActivity_ViewBinding這個類確定是存在的。能夠在以下目錄找到它(這個類是在項目編譯時期由 annotationProcessor 生成的,關於 annotationProcessor 後邊會說到):編程語言

MainActivity_ViewBinding
來看看它裏邊都作了那些事:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view2131165217;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

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

    View view;
    target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class);
    view = Utils.findRequiredView(source, R.id.bt_submit, "method 'submit'");
    view2131165217 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.submit();
      }
    });
  }

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

    target.title = null;

    view2131165217.setOnClickListener(null);
    view2131165217 = null;
  }
}
複製代碼

以前createBinding()方法中return constructor.newInstance(target, source);操做使用的就是MainActivity_ViewBinding類兩個參數的構造函數。

重點看這個構造函數,首先是給target.title賦值,即MainActivity中的TextView,用到了一個findRequiredViewAsType()方法:

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who, Class<T> cls) {
    View view = findRequiredView(source, id, who);
    return castView(view, id, who, cls);
  }
複製代碼

繼續看findRequiredView()方法:

public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
        + " (methods) annotation.");
  }
複製代碼

最終仍是經過findViewById()獲得對應View,而後就是castView()

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }
複製代碼

核心就是把findRequiredView()獲得的 View 轉成指定類型的 View ,若是 xml 中定義的 View 和 Activity 中經過註解綁定的 View 類型不一致,就會拋出上邊方法的異常,可能不少人都遇到過。這樣target.title的賦值就結束了,接下來就是直接使用findRequiredView()找到對應 id 的Button,不用進行類型轉換,而後給它綁定點擊事件,最終調用了在MainActivity中給Button綁定點擊事件時定義的submit()方法。到這裏就完成了 相關 View 的賦值以及事件綁定!

MainActivity_ViewBinding類中還有一個unbind()方法,須要在ActivityFragmentonDestory()中調用,以完成 相關 View 引用的釋放以及點擊事件的解綁操做!

3、註解處理器

那麼,MainActivity_ViewBinding類時如何生成的呢?首先,要生成這個類就要先獲得這個類必須的基礎信息,這就涉及到了annotationProcessor技術,和 APT(Annotation Processing Tool)技術相似,它是一種註解處理器,項目編譯時對源代碼進行掃描檢測找出存活時間爲RetentionPolicy.CLASS的指定註解,而後對註解進行解析處理,進而獲得要生成的類的必要信息,而後根據這些信息動態生成對應的 java 類,至於如何生成 java 類就涉及到了後邊要說的 JavaPoet技術。

這裏咱們先看用註解處理器收集類信息的過程,以前咱們已經在app的 build.gradle引入了 ButterKnife 的註解處理器: butterknife-compiler,其中有一個ButterKnifeProcessor 類完成了註解處理器的核心邏輯。這個類約有1500行代碼,先看下它的基本框架結構:

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
  
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        String sdk = env.getOptions().get(OPTION_SDK_INT);
        ......
        debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
        try {
            trees = Trees.instance(processingEnv);
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Override
    public Set<String> getSupportedOptions() {
        return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    @Override
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            BindingSet binding = entry.getValue();

            JavaFile javaFile = binding.brewJava(sdk, debuggable);
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
            }
        }
        return false;
    }

   @Override
   public SourceVersion getSupportedSourceVersion() {
       return SourceVersion.latestSupported();
   }
}
複製代碼

注意,ButterKnifeProcessor類上使用了@AutoService(Processor.class)註解,來實現註解處理器的註冊,註冊到 javac 後,在項目編譯時就能執行註解處理器了。

ButterKnifeProcessor繼承了AbstractProcessor抽象類,並重寫以上五個方法,若是咱們自定義解處理器也是相似的,看下這幾個方法:

一、init()

首先 init() 方法完成sdk版本的判斷以及相關幫助類的初始化,幫助類主要有如下幾個:

  • Elements elementUtils,註解處理器運行掃描源文件時,以獲取元素(Element)相關的信息。Element 有如下幾個子類: 包(PackageElement)、類(TypeElement)、成員變量(VariableElement)、方法(ExecutableElement)
  • Types typeUtils,
  • Filer filer,用來生成 java 類文件。
  • Trees trees,
二、getSupportedAnnotationTypes()

該方法返回一個Set<String>,表明ButterKnifeProcessor要處理的註解類的名稱集合,即 ButterKnife 支持的註解:butterknife-annotations

三、getSupportedSourceVersion()

返回當前系統支持的 java 版本。

四、getSupportedOptions()

返回註解處理器可處理的註解操做。

五、process()

最後,process() 方法是咱們要重點分析的,在這裏完成了目標類信息的收集並生成對應 java 類。

首先看註解信息的掃描收集,即經過findAndParseTargets()方法:

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

        scanForRClasses(env);
        ......
        ......
        // env.getElementsAnnotatedWith(BindView.class)獲取全部使用BindView註解的元素
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
            try {
                parseBindView(element, builderMap, erasedTargetNames);
            } catch (Exception e) {
                logParsingError(element, BindView.class, e);
            }
        }
        ......
        ......
        // 將builderMap中的數據添加到隊列中
        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);
            // 若是沒找到則保存TypeElement和對應BindingSet
            if (parentType == null) {
                bindingMap.put(type, builder.build());
            } else {
                BindingSet parentBinding = bindingMap.get(parentType);
                if (parentBinding != null) {
                    // 若是找到父類元素,則給當前類元素對應的BindingSet.Builder設置父BindingSet
                    builder.setParent(parentBinding);
                    bindingMap.put(type, builder.build());
                } else {
                    // 再次入隊列
                    entries.addLast(entry);
                }
            }
        }
        return bindingMap;
    }
複製代碼

只保留了BindView註解信息的掃描解析過程,省略其它相似邏輯,先將掃描獲得的註解相關信息保存到builderMaperasedTargetNames中,最後對這些信息進行從新整理返回一個以TypeElement爲 key 、BindingSet爲 value 的 Map,其中TypeElement表明使用了 ButterKnife 的類,即 Activity、Fragment等,BindingSetbutterknife-compiler中的一個自定義類,用來存儲要生成類的基本信息以及註解元素的相關信息。 parseBindView()方法用來解析使用了BindView註解的元素:

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
        // 首先要注意,此時element是VariableElement類型的,即成員變量
        // enclosingElement是當前元素的父類元素,通常就是咱們使用ButteKnife時定義的View類型成員變量所在的類,能夠理解爲以前例子中的MainActivity
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

        // 進行相關校驗
        // 一、isInaccessibleViaGeneratedCode(),先判斷當前元素的是不是private或static類型,
        // 再判斷其父元素是不是一個類以及是不是private類型。
        // 二、isBindingInWrongPackage(),是否在系統相關的類中使用了ButteKnife註解
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
                || isBindingInWrongPackage(BindView.class, element);

        // TypeMirror表示Java編程語言中的一種類型。 類型包括基元類型,聲明的類型(類和接口類型),數組類型,類型變量和空類型。 
        // 還表示了通配符類型參數,可執行文件的簽名和返回類型,以及與包和關鍵字void相對應的僞類型。
        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 的子類,或者是接口,不是的話拋出異常
        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;
        }

        // 得到元素使用BindView註解時設置的屬性值,即 View 對應的xml中的id
        int id = element.getAnnotation(BindView.class).value();
        // 嘗試獲取父元素對應的BindingSet.Builder
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        // QualifiedId記錄了當前元素的包信息以及id
        QualifiedId qualifiedId = elementToQualifiedId(element, id);
        if (builder != null) {
            String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
            // 若是當前id已經被綁定,則拋出異常
            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 {
            // 建立一個新的BindingSet.Builder並返回,而且以enclosingElement 爲key添加到builderMap中
            builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }

        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        // 判斷當前元素是否使用了Nullable註解
        boolean required = isFieldRequired(element);
        // 建立一個FieldViewBinding,它包含了元素名、類型、是不是Nullable
        // 而後和元素id一同添加到BindingSet.Builder
        builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

        // 記錄當前元素的父類元素
        erasedTargetNames.add(enclosingElement);
    }
複製代碼

部分代碼已經寫了註釋,這裏看下getOrCreateBindingBuilder()方法的實現:

private BindingSet.Builder getOrCreateBindingBuilder( Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    // 先判斷enclosingElement對應的BindingSet.Builder是否已存在
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      // 建立一個BindingSet.Builder
      builder = BindingSet.newBuilder(enclosingElement);
      // 添加到builderMap
      builderMap.put(enclosingElement, builder);
    }
    return builder;
  }
複製代碼

因爲用BindingSet保存了註解相關的信息,因此繼續瞭解下它的newBuilder()過程:

static Builder newBuilder(TypeElement enclosingElement) {
    TypeMirror typeMirror = enclosingElement.asType();
    // 判斷當前父元素的類型
    boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
    boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
    boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
      targetType = ((ParameterizedTypeName) targetType).rawType;
    }
    // 獲取父類元素的包名
    String packageName = getPackage(enclosingElement).getQualifiedName().toString();
    // 獲取父類元素的名稱
    String className = enclosingElement.getQualifiedName().toString().substring(
        packageName.length() + 1).replace('.', '$');
    // 即最終要生成的java類的名稱
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
    // 判斷父類元素是否爲final類型
    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
  }
複製代碼

因此BindingSet主要保存了要生成的目標類的基本特徵信息,以及類中使用了 ButterKnife 註解的元素的信息,這樣一個BindingSet就和一個使用了ButterKnife 的類對應了起來。

4、JavaPoet

到這裏要生成的目標類基本信息就收集就完成了,接下來就是生成 java 類文件了,再回到 process()方法:

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingSet binding = entry.getValue();
        // 獲得java類源碼
        JavaFile javaFile = binding.brewJava(sdk, debuggable);
        try {
            // 生成java文件
            javaFile.writeTo(filer);
        } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
        }
    }
    return false;
}
複製代碼

遍歷 bindingMap,根據BindingSet獲得一個JavaFile對象,而後輸入 java 類,這個過程用到了JavaPoet開源庫,提供了一種友好的方式來輔助生成 java 類代碼,同時將類代碼生成文件,不然須要本身拼接字符串來實現,能夠發現BindingSet除了保存信息目標類信息外,還封裝了 JavaPoet 生成目標類代碼的過程。

在繼續往下分析前,先了解下 JavaPoet 中一些重要的類(這些類還有許多實用的方法哦):

  • TypeSpec 表示類、接口、或者枚舉聲明
  • ParameterSpec 表示參數聲明
  • MethodSpec 表示構造函數、方法聲明
  • FieldSpec 表示成員變量,一個字段聲明
  • CodeBlock 表示代碼塊,用來拼接代碼
  • JavaFile 表示Java類的代碼

還有幾個佔位符也瞭解下:

  • **L**,for Literals 替換字符串、原語、JavaPoet中的類型
例如beginControlFlow("for (int i =L; i < L; i++)", 1, 10)
    addStatement("result = resultL i", "+")
  • **S**,for Strings 替換字符串,例如addStatement("returnS", "hello")
  • **T**,for Types 替換類型,例如addStatement("return newT()", Date.class)
  • $N,for Names 替換JavaPoet中的聲明

有了一些基礎概念後,繼續看用 JavaPoet 生成對應JavaFile的過程:

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

用到的JavaFile.builder()方法須要兩個參數:要生成的目標類的包名,以及TypeSpec對象,即createType()方法的返回值,表明一個類、接口、枚舉聲明。結合文章開始的例子,這個TypeSpec對象應表明一個類聲明:

private TypeSpec createType(int sdk, boolean debuggable) {
    // TypeSpec.classBuilder() 方法設置類名稱
    // addModifiers() 方法設置類的修飾符爲 public
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);
    // 若是是final類則添加final修飾符
    if (isFinal) {
      result.addModifiers(FINAL);
    }

    if (parentBinding != null) {
      result.superclass(parentBinding.bindingClassName);
    } else {
      // 讓當前類Unbinder接口,以前生成的MainActivity_ViewBinding類就實現了Unbinder接口
      result.addSuperinterface(UNBINDER);
    }

    if (hasTargetField()) {
      // 添加一個類成員變量,能夠對應到MainActivity_ViewBinding類中的private MainActivity target
      result.addField(targetTypeName, "target", PRIVATE);
    }
    
    // 判斷目標類的類型
    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      // 因爲以前的例子是Activity類型的,因此會走到這裏,去生成MainActivity_ViewBinding類默認的構造函數
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    if (!constructorNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor());
    }
    // 生成view綁定的構造函數,能夠對應到MainActivity_ViewBinding類兩個參數的構造函數
    result.addMethod(createBindingConstructor(sdk, debuggable));

    if (hasViewBindings() || parentBinding == null) {
      // 生成ubinder()方法
      result.addMethod(createBindingUnbindMethod(result));
    }
    return result.build();
  }
複製代碼

這裏重點看下createBindingConstructor()的過程,代碼較多,只保留部分以方便分析:

private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);
    if (hasMethodBindings()) {
      // 給構造函數添加一個修飾符爲final、targetTypeName,名稱爲target的參數,即final MainActivity target
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      constructor.addParameter(targetTypeName, "target");
    }
    if (constructorNeedsView()) {
      // 給構造函數添加一個View類型的source參數
      constructor.addParameter(VIEW, "source");
    } else {
      constructor.addParameter(CONTEXT, "context");
    }
    ......
    if (hasTargetField()) {
      // 給構造函數添加一行this.target = target;的聲明代碼
      constructor.addStatement("this.target = target");
      constructor.addCode("\n");
    }

    if (hasViewBindings()) {
      if (hasViewLocal()) {
        // 給構造函數添加一行View view;的聲明代碼
        constructor.addStatement("$T view", VIEW);
      }
      for (ViewBinding binding : viewBindings) {
        // 根據id查找view並完成賦值
        addViewBinding(constructor, binding, debuggable);
      }
      ......
    }
    ......
    return constructor.build();
  }
複製代碼

那麼是如何根據id查找view呢?答案就在addViewBinding()方法,如下解釋一樣類比MainActivity_ViewBinding兩個參數的構造函數:

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
    if (binding.isSingleFieldBinding()) {
      FieldViewBinding fieldBinding = binding.getFieldBinding();
      // CodeBlock 表示代碼塊,用來完成view查找、賦值語句的拼接
      // 至關於target.title = 
      CodeBlock.Builder builder = CodeBlock.builder()
          .add("target.$L = ", fieldBinding.getName());
      // 因爲例子中title是TextView,不是View因此requiresCast爲true
      boolean requiresCast = requiresCast(fieldBinding.getType());
      // debuggable爲true、fieldBinding.isRequired()爲true,則如下if條件不成立
      if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
        if (requiresCast) {
          builder.add("($T) ", fieldBinding.getType());
        }
        builder.add("source.findViewById($L)", binding.getId().code);
      } else {
        // 繼續給代碼塊添加Utils.find
        builder.add("$T.find", UTILS);
        // 根據上邊的分析可知,會給代碼塊添加RequiredView
        builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
        if (requiresCast) {
          // 給代碼塊添加AsType
          builder.add("AsType");
        }
        // 給代碼塊添加(source, R.id.tv_title
        builder.add("(source, $L", binding.getId().code);
        if (fieldBinding.isRequired() || requiresCast) {
          // 繼續添加view的相關描述,例如field 'title'
          builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
        }
        if (requiresCast) {
          繼續添加view的Class類型,例如TextView.class
          builder.add(", $T.class", fieldBinding.getRawType());
        }
        // 添加右括號,到這裏就完成了view的查找與賦值
        // 例如target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class);
        builder.add(")");
      }
      result.addStatement("$L", builder.build());
      return;
    }
複製代碼

結合對createType()流程的分析,咱們基本瞭解了 MainActivity_ViewBinding 類中構造函數的構建過程、以及 title(以前例子的TextView)的查找賦值的代碼是如何構建出來的,這樣就把註解處理器中 process()方法中BindView註解的處理流程就跑通了。隨然這只是一小部分的分析,但並不妨礙咱們理解其它註解背後的工做流程。

5、小結

能夠看出 ButterKnife 整個過程是在項目編譯階段完成的,主要用到了 annotationProcessor 和 JavaPoet 技術,使用時經過生成的輔助類完成操做,並非在項目運行時經過註解加反射實現的,因此並不會影響項目運行時的性能,可能僅在項目編譯時有略微的影響。

相關文章
相關標籤/搜索