專案需求討論— ButterKnife初級小結

青蛙要fly發表於2019-03-01

前言

在沒有使用DataBinding之前,我的專案都是使用ButterKnife,當然對於ButterKnife大家估計都熟悉的不要太熟悉了。本文我也就當自己的一個總結。

專案需求討論— ButterKnife初級小結

基本使用

基本使用我就直接貼相關的ButterKnife使用教程的文章連結:

ButterKnife官方教程的翻譯

butterknife github連結

從Github連結我們也可以看出有哪些基本的功能註解:

butterknife : android library model 提供android使用的API
butterknife-annotations: java-model,使用時的註解
butterknife-compiler: java-model,編譯時用到的註解的處理器
butterknife-gradle-plugin: 自定義的gradle外掛,輔助生成有關程式碼
butterknife-integration-test: 該專案的測試用例
butterknife-lint: 該專案的lint檢查
sample: demo
複製程式碼

我們同時可以在butterknife-annotations裡面看到基本的註解:

專案需求討論— ButterKnife初級小結

具體流程

我們從二個方面來看:

專案需求討論— ButterKnife初級小結

1.生成XXXX_ViewBinding.java檔案:

專案需求討論— ButterKnife初級小結

public class AAA extends Activity{

    @BindView(R.id.textView)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aaa);
    }
}

複製程式碼

我們寫一個最簡單的Demo,就繫結了一個TextView。(這時候並沒有ButterKnife.bind(this)程式碼) 我們按下Android Studio的編譯按鈕,當編譯完成後,我們檢視檔案:

專案需求討論— ButterKnife初級小結

我們可以發現在編譯後多了一個AAA_ViewBinding.java檔案

public class AAA_ViewBinding implements Unbinder {
  private AAA target;//可以看到這裡的target是AAA這個Activity的例項物件。

  @UiThread
  public AAA_ViewBinding(AAA target) {
    //先呼叫這個一個引數的建構函式,然後呼叫下面的二個引數的建構函式
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public AAA_ViewBinding(AAA target, View source) {
  
    //這裡的target就是AAA這個Activity的例項,
    //source就是上面的單個建構函式生成的AAA這個Activity的DecorView
    /*(ps:一般我們在Activity中setContentView(R.layout.XXX);
          就是在我們寫的佈局設定到了DecorView裡面。)
    */
    
    this.target = target;
    
    //target.tv就是我們寫的AAA裡面定義的TextView tv;
    //通過Utils.findRequiredViewAsType進行tv的例項化,
    /*(ps:因為這裡是要taget.tv,所以如果我們在AAA裡面的TextView用的private就會有問題了
        當然實際private的判斷是在生成AAA_ViewBinding.java這個檔案的時候就會去判斷
    )*/

    target.tv = Utils.findRequiredViewAsType(source, R.id.textView, "field `tv`", TextView.class);
  }

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

    target.tv = null;
  }
}
複製程式碼

我們可以具體看下Utils.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);
  }
  
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.");
  }  
  
複製程式碼

可以看到最後是View view = source.findViewById(id);來幫我們TextView賦值。

可能大家會說。那這個AAA_ViewBinding.java檔案到底是怎麼生成的,為什麼編譯下後,會生成這麼個檔案,為什麼這個檔案裡面相關程式碼也會有等。(初步可以這麼理解,就是遍歷了我們在AAA.java中寫的註解,然後按照相應模板生成一個java檔案。)

詳細的生成AAA_ViewBinding.java檔案可以看以下連結:

深入理解ButterKnife原始碼並掌握原理(一)

深入理解ButterKnife原始碼並掌握原理(二)

深入理解ButterKnife原始碼並掌握原理(三)

2.例項化XXX_ViewBinding.java:

專案需求討論— ButterKnife初級小結

我們在上面的例子中可以看到,在生成的AAA_ViewBinding.java中有了對我們的TextView進行賦值。

那麼現在的問題就是變成了例項化AAA_ViewBinding.java,然後執行相應的這個TextView賦值的相關程式碼.

這時候就需要執行ButterKnife.bind(this);(ps:因為我們這個例子是在Activity中)。我們來看ButterKnife.bind(this)到底執行了什麼:

ButterKnife — > bind:

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
   //target是我們傳入的this,也就是AAA這個Activity的例項
   //獲取到AAA的DecorView例項化物件sourceView
   View sourceView = target.getWindow().getDecorView();
   //呼叫createBinding方法
   return createBinding(target, sourceView);
}
複製程式碼

createBinding:

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {

    //1.獲取AAA的Class
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    
    //2.通過findBindingConstructorForClass獲得構造器
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

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

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
    
      //3. 通過構造器例項化
      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);
    }
  }
複製程式碼

我們發現主要是第二步,獲取構造器,所以我們來看下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 {
    
        //載入了(clsName + "_ViewBinding")的類,也就是AAA_ViewBinding的Class物件,
        //然後在用這個AAA_ViewBinding的Class物件獲取構造器。
        
      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;
  }
複製程式碼

所以當執行了ButterKnife最後就是通過建構函式獲取了AAA_ViewBinding的例項,而在AAA_ViewBinding.java的建構函式中對TextView進行了賦值。

結尾

如果哪裡有問題,歡迎大家留言提出。我可以及時改正,(^▽^)

相關文章