Android從上車到漂移之ButterKnife完全解析

一、前言

ButterKnife——通過注解的方式生成View字段巷挥、資源綁定和方法綁定的樣板代碼梧奢,是一款老司機書寫UI布局的必備神器!自從有了ButterKnife,媽媽再也不用擔(dān)心我findViewbyid()穆刻,find到手抽筋。

本文基于最新的8.7.0版本進行分析派敷,不同版本可能實現(xiàn)方式有所差異蛹批,請知悉。

二篮愉、上車

下載Android studio 插件Android ButterKnife Zelezny腐芍,一鍵生成模板代碼。更多姿勢试躏,請參考官方文檔猪勇。作為一個老司機,上車不是我們的重點颠蕴,漂移才是我們的目標(biāo)泣刹!

三、漂移

checkout下來源碼犀被,工程結(jié)構(gòu)如圖椅您,核心實現(xiàn)模塊是butterknife、butterknife-annotations寡键、butterknife-compiler掀泳。

butterknife工程結(jié)構(gòu)圖.png

首先從入口開始,以activity的bind為例,代碼如下:

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

先獲取activity的根布局DecorView员舵,然后作為參數(shù)傳遞給createBinding()方法脑沿,該方法就是通過構(gòu)造函數(shù)new出一個“clsName + "_ViewBinding”的class對象÷砥В看一下createBinding()方法的關(guān)鍵代碼:

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);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
  }

繼續(xù)追蹤 findBindingConstructorForClass()的源碼:

  @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這個LinkedHashMap中查找緩存庄拇,如果命中,直接返回bindingCtor韭邓,bindingCtor是實現(xiàn)了Unbinder接口的子類的構(gòu)造函數(shù)措近;如果為null,則通過ClassLoade加載一個"clsName + "_ViewBinding""的類仍秤,然后返回其構(gòu)造函數(shù)熄诡。其中clsName 就是我們上文調(diào)用bind()的activity,最后把它put到BINDINGS這個集合中诗力。
??那么"clsName + "_ViewBinding""的這個類在哪碑韵?里面實現(xiàn)了什么邏輯胜宇?又是怎么生成的?這些才是今天的重點摧冀!接下來我們一個個解答:

3.1 "clsName + "_ViewBinding""的這個類在哪菜拓?:

clsName + "_ViewBinding生成路徑.jpg

大家根據(jù)截圖一層一層追進去就可以找到相應(yīng)代碼瓣窄。大致是:app->build->generated->source->apt->"打包渠道"->clsName類在工程中所在目錄

3.2 "clsName + "_ViewBinding""的這個類實現(xiàn)了什么邏輯?

如上所述纳鼎,該類實現(xiàn)了Unbinder接口俺夕,有兩個重載的構(gòu)造函數(shù),和一個unbind()方法贱鄙,unbind()方法很簡單劝贸,顧名思義就是解除綁定,釋放資源逗宁,沒啥好說的映九。

public class BusinessmenListActivity_ViewBinding implements Unbinder {
  private BusinessmenListActivity target;

  @UiThread
  public BusinessmenListActivity_ViewBinding(BusinessmenListActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public BusinessmenListActivity_ViewBinding(BusinessmenListActivity target, View source) {
    this.target = target;

    target.mRecyclerView = Utils.findRequiredViewAsType(source, R.id.rv_bus, "field 'mRecyclerView'", RecyclerView.class);
    target.mRefreshLayout = Utils.findRequiredViewAsType(source, R.id.refresh_layout, "field 'mRefreshLayout'", SwipeRefreshLayout.class);
  }

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

    target.mRecyclerView = null;
    target.mRefreshLayout = null;
  }
}

我們重點看一下Utils.findRequiredViewAsType()的代碼,就是這個方法幫我們實現(xiàn)了綁定View相關(guān)邏輯。先調(diào)用findRequiredView()方法返回Viwe對象,然后再castView()成具體的子View瞎颗。比較簡單件甥,直接看代碼相信都能看懂:

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

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

至此,就完成了View綁定的所有邏輯哼拔。

3.3 "clsName + "_ViewBinding"".java文件是如何生成的引有?

該類的生成主要依賴一個叫做APT的工具。APT(Annotation Processing Tool)是一種處理注解的工具,它對源代碼文件進行檢測找出其中的Annotation倦逐,使用Annotation進行額外的處理譬正。
??使用apt需要繼承AbstractProcessor類,同時有幾個核心方法需要實現(xiàn),分別是:
??init()主要做一些初始化操作导帝;
??getSupportedAnnotationTypes()守谓,顧名思義,獲取所有支持的注解類型您单;
??process()處理注解相關(guān)邏輯斋荞,"clsName + "_ViewBinding"".java文件生成的核心邏輯就在這里!

在ButterKnife中虐秦,有一個ButterKnifeProcessor類平酿,該類就是處理ButterKnife注解相關(guān)邏輯的類。

3.3.1 我們先從init()方法開始:初始化悦陋。

  @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.");
      }
    }

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

init()方法中蜈彼,沒有過多邏輯,只是有幾個變量需要說明一下俺驶,這幾個主要是注解處理時用到的工具類幸逆。

  private Elements elementUtils;
  private Types typeUtils;
  private Filer filer;
  private Trees trees;

Elements:一個用來處理Element的工具類,源代碼的每一個部分都是一個特定類型的Element暮现。例如还绘,包名、字段栖袋、方法等等拍顷。
Types:一個用來處理TypeMirror的工具類,比如判斷該元素是class還是interface塘幅;
Filer:生成文件昔案;
Trees :樹,遍歷文件用到电媳。

3.3.2 接下來看一下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(BindAnim.class);
    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(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }

該方法的大概意思就是,將所有ButterKnife用到的注解全部添加到支持的注解集合中匆背。

我們來瞄一眼最熟悉的BindView.class

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

代碼很簡單呼伸,但是有幾個注解相關(guān)的點需要說明一下,

元注解:元注解的作用就是負責(zé)注解其他注解钝尸。相關(guān)元注解:

1.@Target:

表示注解類型所適用的程序元素的種類括享。

2.@Retention:

表示該注解類型的注解保留的時長。
??SOURCE 僅存在Java源文件珍促,經(jīng)過編譯器后便丟棄相應(yīng)的注解铃辖;
??CLASS 存在Java源文件,以及經(jīng)編譯器后生成的Class字節(jié)碼文件猪叙,但在運行時VM不再保留注釋娇斩;
??RUNTIME 存在源文件仁卷、編譯生成的Class字節(jié)碼文件,以及保留在運行時VM中犬第,可通過反射性地讀取注解锦积。

對應(yīng)到BindView這個注解中,我們可以知道歉嗓,該注解適用的類型為字段丰介,且會打包到Class字節(jié)碼文件中,該注解接收的值類型為@IdRes int類型鉴分。

3.3.3 最后process()方法:遍歷所有注解->根據(jù)注解生成相應(yīng)的代碼->生成java文件哮幢。


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

先瞄一眼findAndParseTargets()方法核心代碼:

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

  // ...... 此處省略若干行代碼

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

// 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;

還是以@BindView 為例,其他的類似志珍,不再一一贅述橙垢。關(guān)鍵代碼:

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

遍歷所有使用@BindView注解的Element,繼續(xù)看parseBindView()代碼:

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

    // Assemble information on the field.

//獲取綁定的View的id伦糯,即:R.id.xx.

    int id = element.getAnnotation(BindView.class).value();

//判斷該元素是否已經(jīng)綁定過柜某,如果綁定過,返回錯誤舔株,否則莺琳,調(diào)用getOrCreateBindingBuilder()方法
    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);
  }

首先,通過 int id = element.getAnnotation(BindView.class).value();獲取綁定的View的id载慈,即:R.id.xx.;

再通過elementToQualifiedId()方法,生成一個合格標(biāo)識:

  private QualifiedId elementToQualifiedId(Element element, int id) {
    return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);
  }

第三步,從已經(jīng)綁定的元素中查找該元素是否存在珍手,如果存在办铡,返回錯誤,不允許重復(fù)綁定琳要,否則調(diào)用getOrCreateBindingBuilder()方法生成一個綁定對象:

  private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
  }

進一步看看 BindingSet.newBuilder(enclosingElement)方法

  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('.', '$');
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
  }

就是這里將生成的java文件類名定義為“className + "_ViewBinding"”

第四步寡具,回到parseBindView()方法,為綁定對象添加綁定的View字段:

    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

第五步稚补,關(guān)聯(lián)父類綁定的資源(view童叠、string、listener等)课幕,并把她們添加到 Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>()這個集合當(dāng)中厦坛,這個集合存儲了所有的BuildSet對象。

BuildSet對象就是單個綁定類型(activity乍惊、fragment杜秸、view、dialog)的所有綁定請求(BindView润绎、BindString等)的集合撬碟。每一個使用ButterKnife的類對應(yīng)一個BuildSet诞挨。

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

主要看一下關(guān)鍵代碼 BindingSet build()代碼,以及BindingSet的構(gòu)造函數(shù):

    BindingSet build() {
      ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
      for (ViewBinding.Builder builder : viewIdMap.values()) {
        viewBindings.add(builder.build());
      }
      return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
          viewBindings.build(), collectionBindings.build(), resourceBindings.build(),
          parentBinding);
    }

 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īng)添加到集合當(dāng)中惶傻,只差生成代碼,可謂萬事俱備只欠東風(fēng)其障!

回到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();

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

通過第一行代碼银室,我們幾經(jīng)周折終于掌握了她的來龍去脈,還剩一個for循環(huán)静秆。for循環(huán)無非就是遍歷生成相應(yīng)的"clsName + "_ViewBinding"".java文件粮揉,具體怎么生成的,我們跟進去瞄一眼:

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

這里涉及一個重要的知識點:JavaPoet抚笔。此為何物扶认? 套用官方的簡介就是:“JavaPoet is a Java API for generating .java source files.” 簡直精辟得不能再精辟!

簡單理解——就是用來生成java文件的殊橙,這不正是我們所要的東風(fēng)嗎辐宾?具體的使用姿勢,不是本文的重點膨蛮,可以查看官方文檔叠纹,文檔寫得相當(dāng)詳細,在此不再一一贅述敞葛,本文只對用到的地方做一些解析誉察。

看一下createType()方法:

  private TypeSpec createType(int sdk, boolean debuggable) {
    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);
    }

    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      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());
    }
    result.addMethod(createBindingConstructor(sdk, debuggable));

    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
  }

如果你已經(jīng)了解了JavaPoet之后再來看這個代碼,其實可以一目了然惹谐,這里不做過多解釋持偏,僅僅驗證一下,我們的設(shè)想與生成的"clsName + "_ViewBinding"".java文件內(nèi)容是否一致即可氨肌。

主要看一下createBindingConstructor()方法鸿秆,該方法是生成"clsName + "_ViewBinding"".java代碼的核心:

  private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);

    if (hasMethodBindings()) {
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      constructor.addParameter(targetTypeName, "target");
    }

    if (constructorNeedsView()) {
      constructor.addParameter(VIEW, "source");
    } else {
      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());
    }

    if (hasOnTouchMethodBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
          .addMember("value", "$S", "ClickableViewAccessibility")
          .build());
    }

    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);
      }
      for (ViewBinding binding : viewBindings) {
        addViewBinding(constructor, binding, debuggable);
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        constructor.addStatement("$L", binding.render(debuggable));
      }

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

大功告成!所有的邏輯執(zhí)行完畢之后怎囚,就生成是我們3.2節(jié)對應(yīng)的代碼卿叽。

4、總結(jié)

祝各位司機漂移成功!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恳守,一起剝皮案震驚了整個濱河市考婴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌井誉,老刑警劉巖蕉扮,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颗圣,居然都是意外死亡喳钟,警方通過查閱死者的電腦和手機屁使,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奔则,“玉大人蛮寂,你說我怎么就攤上這事∫撞纾” “怎么了酬蹋?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抽莱。 經(jīng)常有香客問我范抓,道長,這世上最難降的妖魔是什么食铐? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任匕垫,我火速辦了婚禮,結(jié)果婚禮上虐呻,老公的妹妹穿的比我還像新娘象泵。我一直安慰自己,他們只是感情好斟叼,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布偶惠。 她就那樣靜靜地躺著,像睡著了一般朗涩。 火紅的嫁衣襯著肌膚如雪忽孽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天谢床,我揣著相機與錄音扒腕,去河邊找鬼。 笑死萤悴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的皆的。 我是一名探鬼主播覆履,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼费薄!你這毒婦竟也來了硝全?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤楞抡,失蹤者是張志新(化名)和其女友劉穎伟众,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體召廷,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡凳厢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年账胧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片先紫。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡治泥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遮精,到底是詐尸還是另有隱情居夹,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布本冲,位于F島的核電站准脂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏檬洞。R本人自食惡果不足惜狸膏,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疮胖。 院中可真熱鬧环戈,春花似錦、人聲如沸澎灸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽性昭。三九已至拦止,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糜颠,已是汗流浹背汹族。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留其兴,地道東北人顶瞒。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像元旬,于是被迫代替她去往敵國和親榴徐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理匀归,服務(wù)發(fā)現(xiàn)坑资,斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 1穆端、寫在前面 在小歆記賬WebApp和小歆記賬AUI中使用了一些正則來簡化代碼袱贮。在PHP和javascript中使...
    小歆Pro閱讀 698評論 0 0
  • 線性規(guī)劃LP 適用條件 解滿足一定的約束條件 在所有滿足約束的可能解中,根據(jù)某個定義良好的評判標(biāo)準体啰,該解是最優(yōu)的 ...
    芥丶未央閱讀 1,357評論 0 0
  • 郭大年閱讀 392評論 0 0