1. 程式人生 > >Android從上車到漂移之ButterKnife完全解析

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

一、前言

  ButterKnife——通過註解的方式生成View欄位、資源繫結和方法繫結的樣板程式碼,是一款老司機書寫UI佈局的必備神器!自從有了ButterKnife,媽媽再也不用擔心我findViewbyid(),find到手抽筋。

  本文基於最新的8.7.0版本進行分析,不同版本可能實現方式有所差異,請知悉。

二、上車

  下載Android studio 外掛Android ButterKnife Zelezny,一鍵生成模板程式碼。更多姿勢,請參考官方文件。作為一個老司機,上車不是我們的重點,漂移才是我們的目標!

三、漂移

  checkout下來原始碼,工程結構如圖,核心實現模組是butterknife、butterknife-annotations、butterknife-compiler。

butterknife工程結構圖.png

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

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

先獲取activity的根佈局DecorView,然後作為引數傳遞給createBinding()方法,該方法就是通過建構函式new出一個“clsName + “_ViewBinding”的class物件。看一下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); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); }

繼續追蹤 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是實現了Unbinder介面的子類的建構函式;如果為null,則通過ClassLoade載入一個”clsName + “_ViewBinding”“的類,然後返回其建構函式。其中clsName 就是我們上文呼叫bind()的activity,最後把它put到BINDINGS這個集合中。
  那麼”clsName + “_ViewBinding”“的這個類在哪?裡面實現了什麼邏輯?又是怎麼生成的?這些才是今天的重點!接下來我們一個個解答:

3.1 “clsName + “_ViewBinding”“的這個類在哪?:

clsName + "_ViewBinding生成路徑.jpg
大家根據截圖一層一層追進去就可以找到相應程式碼。大致是:app->build->generated->source->apt->”打包渠道”->clsName類在工程中所在目錄

3.2 “clsName + “_ViewBinding”“的這個類實現了什麼邏輯?

如上所述,該類實現了Unbinder介面,有兩個過載的建構函式,和一個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()的程式碼,就是這個方法幫我們實現了繫結View相關邏輯。先呼叫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類,同時有幾個核心方法需要實現,分別是:
  init()主要做一些初始化操作;
  getSupportedAnnotationTypes(),顧名思義,獲取所有支援的註解型別;
  process()處理註解相關邏輯,”clsName + “_ViewBinding”“.java檔案生成的核心邏輯就在這裡!

  在ButterKnife中,有一個ButterKnifeProcessor類,該類就是處理ButterKnife註解相關邏輯的類。

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

程式碼很簡單,但是有幾個註解相關的點需要說明一下,

元註解:元註解的作用就是負責註解其他註解。相關元註解:

[email protected]

  表示註解型別所適用的程式元素的種類。

[email protected]

表示該註解型別的註解保留的時長。
  SOURCE 僅存在Java原始檔,經過編譯器後便丟棄相應的註解;
  CLASS 存在Java原始檔,以及經編譯器後生成的Class位元組碼檔案,但在執行時VM不再保留註釋;
  RUNTIME 存在原始檔、編譯生成的Class位元組碼檔案,以及保留在執行時VM中,可通過反射性地讀取註解。

   對應到BindView這個註解中,我們可以知道,該註解適用的型別為欄位,且會打包到Class位元組碼檔案中,該註解接收的值型別為@IdRes int型別。

3.3.3 最後process()方法:遍歷所有註解->根據註解生成相應的程式碼->生成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 為例,其他的類似,不再一一贅述。關鍵程式碼:

// 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,繼續看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();

//判斷該元素是否已經繫結過,如果繫結過,返回錯誤,否則,呼叫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()方法,生成一個合格標識:

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

第三步,從已經繫結的元素中查詢該元素是否存在,如果存在,返回錯誤,不允許重複繫結,否則呼叫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));

第五步,關聯父類繫結的資源(view、string、listener等),並把她們新增到 Map

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

主要看一下關鍵程式碼 BindingSet build()程式碼,以及BindingSet的建構函式:

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

至此,所有需要繫結的資源已經新增到集合當中,只差生成程式碼,可謂萬事俱備只欠東風!

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

通過第一行程式碼,我們幾經周折終於掌握了她的來龍去脈,還剩一個for迴圈。for迴圈無非就是遍歷生成相應的”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檔案的,這不正是我們所要的東風嗎?具體的使用姿勢,不是本文的重點,可以檢視官方文件,文件寫得相當詳細,在此不再一一贅述,本文只對用到的地方做一些解析。

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

如果你已經瞭解了JavaPoet之後再來看這個程式碼,其實可以一目瞭然,這裡不做過多解釋,僅僅驗證一下,我們的設想與生成的”clsName + “_ViewBinding”“.java檔案內容是否一致即可。

主要看一下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();
  }

大功告成!所有的邏輯執行完畢之後,就生成是我們3.2節對應的程式碼。

4、總結

  祝各位司機漂移成功!

相關推薦

Android上車漂移ButterKnife完全解析

一、前言   ButterKnife——通過註解的方式生成View欄位、資源繫結和方法繫結的樣板程式碼,是一款老司機書寫UI佈局的必備神器!自從有了ButterKnife,媽媽再也不用擔心我findViewbyid(),find到手抽筋。     本文基於

Android 外掛化入手指南classLoader完全解析

原文:https://blog.csdn.net/u012124438/article/details/53235848  Java程式碼都是寫在Class裡面的,程式執行在虛擬機器上時,虛擬機器需要把需要的Class載入進來才能建立例項物件並工作,而完成這一個載入工作的角色就是Cla

Android外掛化學習路(二)ClassLoader完全解析

Java程式碼都是寫在Class裡面的,程式執行在虛擬機器上時,虛擬機器需要把需要的Class載入進來才能建立例項物件並工作,而完成這一個載入工作的角色就是ClassLoader。 類載入器ClassLoader介紹 Android的Dalvik/ART虛擬

Android 多線程IntentService 完全詳解

required xmlns 抽象 bitmap 圖片 on() 使用 ecif ati 關聯文章: Android 多線程之HandlerThread 完全詳解 Android 多線程之IntentService 完全詳解 android多線程-AsyncTask之

Android新特性介紹,ConstraintLayout完全解析

mat 區別 界面 -s 自己 解決 match roo pre 本篇文章的主題是ConstraintLayout。其實ConstraintLayout是Android Studio 2.2中主要的新增功能之一,也是Google在去年的I/O大會上重點宣傳的一個功能。我們都

Android自助餐】Handler訊息機制完全解析(二)MessageQueue的佇列管理

Android自助餐Handler訊息機制完全解析(二)MessageQueue的佇列管理 Android自助餐Handler訊息機制完全解析二MessageQueue的佇列管理 新增到訊息佇列enqueueMessage 從佇

Android自助餐】Handler訊息機制完全解析(五)鳥瞰與總結

Android自助餐Handler訊息機制完全解析(五)鳥瞰與總結 Android自助餐Handler訊息機制完全解析五鳥瞰與總結 Message MessageQueue Handler Looper

Android自助餐】Handler訊息機制完全解析(四)Looper解析

Android自助餐Handler訊息機制完全解析(四)Looper解析 Android自助餐Handler訊息機制完全解析四Looper解析 Looper 初始化prepare 提供loope

Android 多執行緒HandlerThread 完全詳解

  之前對執行緒也寫過幾篇文章,不過倒是沒有針對android,因為java與android線上程方面大部分還是相同,不過本篇我們要介紹的是android的專屬類HandlerThread,因為HandlerThread在設定思想上還是挺值得我們學習的,那麼我們下面來

Android 開源框架Universal-Image-Loader完全解析(一)--- 基本介紹及使用

                大家好!差不多兩個來月沒有寫文章了,前段時間也是在忙換工作的事,準備筆試面試什麼的事情,現在新工作找好了,新工作自己也比較滿意,唯一遺憾的就是自己要去一個新的城市,新的環境新的開始,希望自己能儘快的適應新環境,現在在準備交接的事情,自己也有一些時間了,所以就繼續給大家分享And

Android FM模組學習四原始碼解析(三)

     由於最近一直忙專案,沒有時間來更新文件,今天抽空來寫一點,希望大家可以學習使用!      這一章當然還是來分析FM模組的原始碼。FmReceiver.java publicFmReceiver(String devicePath,FmRxEvCallbacks

Android 開源框架Universal-Image-Loader完全解析(二)--- 圖片快取策略詳解

本篇文章繼續為大家介紹Universal-Image-Loader這個開源的圖片載入框架,介紹的是圖片快取策略方面的,如果大家對這個開源框架的使用還不瞭解,大家可以看看我之前寫的一篇文章Android 開源框架Universal-Image-Loader完全解析(一)---

android零單排手機通訊錄的讀取、新增、刪除、查詢

工作中牽扯到了對android手機通訊錄的系列操作,網上查找了好多資料,大多數都介紹的是模模糊糊,終於找到了一篇靠譜的,於是摘抄下來,大家共同學習:</span> Android聯絡人資料庫檔案(contact2.db) 有研究過手機通訊錄資料的童鞋肯定

Android N App分屏模式完全解析(下)

在上篇中,介紹了什麼是App分屏模式,以及如何設定我們的App來進入分屏模式。這次我們看一下,作為開發者,我們應該如何讓自己的App進入分屏模式,當App進入分屏模式時,我們注意哪些問題。 簡單地說,我認為除了保證分屏時App功能、效能正常以外,我們需要重點學習 如何在分屏模式下開啟新的Activity 以

Android零單排免費簡訊驗證

介紹 簡訊驗證功能大家都很熟悉了。在很多地方都能見到,註冊新使用者或者短息驗證支付等。簡訊驗證利用簡訊驗證碼來註冊會員,大大降低了非法註冊,很大程度上提高了使用者賬戶的安全性。    目前市面上已經

Android FM模組學習四原始碼解析(四)

     我今天想分享的是FM模組的儲存方法,即FmSharedPreferences.java FmSharedPreferences(Context context)在構造方法中載入Load()方法, public void  Load(){       Log.d(

Android FM模組學習四原始碼解析(一)

  前一章我們瞭解了FM手動調頻,接下來我們要分析FM模組用到的原始碼。此原始碼是基於高通平臺的,別的平臺都大同小異,只不過是平臺自己作了些小改動而已。    首先要看的當然是主activity,

C語言重點——指標篇(一文讓你完全搞懂指標)| 記憶體理解指標 | 指標完全解析

> 有乾貨、更有故事,微信搜尋【**程式設計指北**】關注這個不一樣的程式設計師,等你來撩~ **注:這篇文章好好看完一定會讓你掌握好指標的本質** C語言最核心的知識就是指標,所以,這一篇的文章主題是「指標與記憶體模型」 說到指標,就不可能脫離開記憶體,學會指標的人分為兩種,一種是不瞭解記憶體

一篇好文Android資料庫GreenDao的完全解析

今日科技快訊 小米近日稱,財政部此次公告的檢查為2017年財政部會計監督檢查,是針對2016年的會計資訊質量進行的檢查。公司存在部分費用攤銷核算錯誤、對外贈送商品未作為視同銷售行為申報繳稅、報銷發票管理不規範、費用管理制度不完善等問題。以上問題均已整改完成,並獲得財政部

Android進階——效能優化程序拉活原理及手段完全解析(二)

引言 上一篇文章Android進階——效能優化之程序保活原理及手段完全解析(一)總結了Android程序和執行緒的相關知識,主要介紹了幾種提升程序優先順序的手段,通常僅僅是提高優先順序只能讓你的程序存活時間久一點,但是真正的被殺死之後就不會自動拉活的,如果你的程