Android ButterKnife入門到放棄

世界公認(rèn)最高效的學(xué)習(xí)方法

  1. 選擇一個(gè)你要學(xué)習(xí)的內(nèi)容

  2. 想象如果你要將這些內(nèi)容教授給一名新人,該如何講解

  3. 如果過程中出了問題,重新回顧這個(gè)內(nèi)容

  4. 簡化:容你的講解越來越簡單易懂 ————理查德費(fèi)曼學(xué)習(xí)法

目錄
一、前言
二、簡介
三、ButterKnife入門
(1)是什么?
(2)有什么用?
(3)為什么要用?
四、ButterKnife渡劫
(4)怎么用?
五、ButterKnife封神之路
(5)原理是什么?
(6)輔助插件
六、總結(jié)
七、內(nèi)容推薦
八、項(xiàng)目參考


一、前言

以前一直在寫一些常用的功能模塊,如何使用及封裝。后來發(fā)現(xiàn)網(wǎng)上一搜一大堆,關(guān)鍵是寫得比我好也就算了,更過分的是字?jǐn)?shù)比我還多,簡直不讓人活了。后來自己就搞了個(gè)工具Demo項(xiàng)目,把常用的功能都弄上去。也不打算寫這類文章,如果朋友們在開發(fā)功能上遇到障礙可以去項(xiàng)目(文章最下面)找找,說不定就有了。

如今只能被逼改行給大家介紹一些Android目前比較火的開源庫。可以添加到項(xiàng)目當(dāng)中,提高開發(fā)效率,并讓項(xiàng)目開發(fā)起來更輕松方便,易與維護(hù)。提高逼格.... (又在胡扯)

二、簡介

本篇文章要給大家介紹的是最容易使用,也是最簡單的ButterKnife。用過的人都知道為什么好用,沒有過的也不用后悔,現(xiàn)在學(xué)了也不吃虧,花10分鐘看完本篇文章就懂了。 (10分鐘你買不了吃虧,也買不了上當(dāng))

在開始講之前給大家看看大綱,也許一眼你就學(xué)會(huì)了也說不一定

本篇圍繞幾個(gè)問題展開:

  1. 是什么?
  2. 有什么用?
  3. 為什么要用?
  4. 怎么用?
  5. 原理是什么?

等明白這幾個(gè)問題后(就可以渡劫了),在給大家介紹個(gè)好用的輔助插件。

?三、ButterKnife入門

(1)是什么?

一句話:是出自JakeWharton大神開源的一個(gè)依賴注入庫,通過注解的方式來替代android中view的相關(guān)操作。

那么問題來了:什么是依賴注入? 這里簡單介紹介紹一下,忘了是哪個(gè)博客CP過來的了 。

什么是依賴注入?

依賴注入通俗的理解就是,當(dāng)A類需要引用B類的對象時(shí),將B類的對象傳入A類的過程就是依賴注入。依賴注入最重要的作用就是解耦,降低類之間的耦合,保證代碼的健壯性、可維護(hù)性、可擴(kuò)展性。

常用的實(shí)現(xiàn)方式有構(gòu)造函數(shù)、set方法、實(shí)現(xiàn)接口等。例如:

// 通過構(gòu)造函數(shù)
public class A {
    B b;
    public A(B b) {
        this.b = b;
    }
}

// 通過set方法
public class A {
    B b;
    public void setB(B b) {
        this.b = b;
    }
}

(2)有什么用?

一句話:減少大量的findViewById以及setOnClickListener代碼,且對性能的影響較小。

(3)為什么要用?

一句話:使用簡單,容易上手、學(xué)習(xí)成本低、對性能影響小

四、ButterKnife渡劫

(4)怎么用?

1.添加依賴

    android {
      ...
      // Butterknife requires Java 8.
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
      }
    }

    dependencies {
      implementation 'com.jakewharton:butterknife:10.2.0'
      annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
      //若是kotlin 使用kapt 代替annotationProcessor 
    }

如果要在庫中使用,將插件添加到buildscript

 buildscript {
      repositories {
        mavenCentral()
        google()
       }
      dependencies {
        //classpath 'com.android.tools.build:gradle:3.3.0'
        classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.0'
      }
    }

并在moudle添加這些代碼

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

在庫中使用R2代替R

class ExampleActivity extends Activity {
  @BindView(R2.id.user) EditText username;
  @BindView(R2.id.pass) EditText password;
...
}

如果不需要再庫中使用 ,直接依賴第一塊代碼既可

2.綁定布局

①Activity

//Activity中的使用
class ExampleActivity extends Activity {
  @BindView(R.id.title) TextView title;
  @BindView(R.id.subtitle) TextView subtitle;
  @BindView(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

②Fragment

//Fragment使用
public class FancyFragment extends Fragment {
  @BindView(R.id.button1) Button button1;
  @BindView(R.id.button2) Button button2;

  @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container, false);
    ButterKnife.bind(this, view);
    // TODO Use fields...
    return view;
  }
}

③適配器

//適配器中使用
public class MyAdapter extends BaseAdapter {
  @Override public View getView(int position, View view, ViewGroup parent) {
    ViewHolder holder;
    if (view != null) {
      holder = (ViewHolder) view.getTag();
    } else {
      view = inflater.inflate(R.layout.whatever, parent, false);
      holder = new ViewHolder(view);
      view.setTag(holder);
    }

    holder.name.setText("John Doe");
    // etc...

    return view;
  }

  static class ViewHolder {
    @BindView(R.id.title) TextView name;
    @BindView(R.id.job_title) TextView jobTitle;

    public ViewHolder(View view) {
      ButterKnife.bind(this, view);
    }
  }
}

④其他對象

//其他類中可以通過
View view = inflater.inflate(R.layout.fancy_fragment, null);
ButterKnife.bind(this, view);

通過ButterKnife.bind()方法直接跟對象綁定在一起,之后給控件添加注釋 表示這個(gè)控件已經(jīng)和對象綁定。就可以直接使用了
3.聲明并使用

①綁定控件

//第一種方式:@BindView

@BindView(R.id.tv_hell_world)
TextView tvHellWorld;
//使用方式:
tvHellWorld.setText("Hello world");

//第二種方式:@@BindViews
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List nameViews;

//很遺憾的是最新版已經(jīng)找不到該方法了  只能當(dāng)List使用
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);

static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
  @Override public void apply(View view, int index) {
    view.setEnabled(false);
  }
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
  @Override public void set(View view, Boolean value, int index) {
    view.setEnabled(value);
  }
};

ButterKnife.apply(nameViews, View.ALPHA, 0.0f);

②綁定資源

//values文件里面的資源綁定 
@BindString  聲明:@BindString(R.string.title) String title;
@BindDrawable 聲明: @BindDrawable(R.drawable.graphic) Drawable graphic
@BindColor 聲明:@BindColor(R.color.red) int red;
@BindDimen 聲明:@BindDimen(R.dimen.spacer) float spacer;

③綁定監(jiān)聽事件

@OnClick

//單個(gè)點(diǎn)擊事件
@OnClick(R.id.submit)
public void submit(View view) {
  // TODO submit data to server...
}
//多個(gè)點(diǎn)擊事件
@OnClick({R.id.btn_send,R.id.btn_close,R.id.btn_canle})
    public void onViewClicked(View view) {
        switch (view.getId()){
            case R.id.btn_send:
                break;
            case R.id.btn_close:
                break;
            case R.id.btn_canle:
                break;
        }
    }

方法參數(shù)可變

//無參數(shù)情況
@OnClick(R.id.submit)
public void submit() {
  // TODO submit data to server...
}
//指定特定類型參數(shù)情況
@OnClick(R.id.submit)
public void sayHi(Button button) {
  button.setText("Hello!");
}

④多種監(jiān)聽器

//選中與未選中監(jiān)聽
@OnItemSelected(R.id.list_view)
void onItemSelected(int position) {
  // TODO ...
}

@OnItemSelected(value = R.id.maybe_missing, callback = NOTHING_SELECTED)
void onNothingSelected() {
  // TODO ...
}

這里只介紹比較常用的幾個(gè)注解,更多注解可以查看源碼。 使用方法大同小異

4.解綁

//Fragment具有不同于activity的生命周期,需要在合適的生命周期中進(jìn)行解綁
public class FancyFragment extends Fragment {
  @BindView(R.id.button1) Button button1;
  @BindView(R.id.button2) Button button2;
  private Unbinder unbinder;

  @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container, false);
    unbinder = ButterKnife.bind(this, view);
    // TODO Use fields...
    return view;
  }

  @Override public void onDestroyView() {
    super.onDestroyView();
    unbinder.unbind();
  }
}

根據(jù)文檔提示Fragment具有不同于Activity的生命周期。在onCreateView中綁定一個(gè)Fragment時(shí),在onDestroyView中將視圖設(shè)置為null。當(dāng)您為您調(diào)用bind時(shí),Butter Knife返回一個(gè)Unbinder實(shí)例。在適當(dāng)?shù)纳芷诨卣{(diào)中調(diào)用它的unbind方法

5.可選綁定

①默認(rèn)情況下,@Bind和listener綁定都是必需的。如果找不到目標(biāo)視圖,將引發(fā)異常

②要禁止這種行為并創(chuàng)建可選綁定,請向字段添加@Nullable注釋或向方法添加@Optional注釋

③使用方式


@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;

@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
  // TODO ...
}

5.注意事項(xiàng)

①ButterKnife.bind(this);必須在setContentView();之后調(diào)用;且父類綁定后,子類不需要再綁定

②在非Activity 類(eg:Fragment、ViewHold)中綁定: ButterKnife.bind(this,view);這里的this不能替換成getActivity()

③使用ButterKnife修飾的方法和控件,不能用private or static 修飾,否則會(huì)報(bào)錯(cuò)。

④文檔提示:Fragment中使用綁定時(shí) ,需要再onDestroyView()中進(jìn)行解綁

6.混淆

最后,別忘了在 proguard-rules.pro文件中加入混淆代碼,確保在混淆后仍可以繼續(xù)運(yùn)行。

-keep public class * implements butterknife.Unbinder { public <init>(**, android.view.View); }
-keep class butterknife.*
-keepclasseswithmembernames class * { @butterknife.* <methods>; }
-keepclasseswithmembernames class * { @butterknife.* <fields>; }

看到這里,若還有不會(huì)的。建個(gè)項(xiàng)目照著步驟來一次,5分鐘就可搞定。也是最容易集成的庫之一。不試一試怎么知道好不好用。

當(dāng)學(xué)會(huì)了使用之后一起來研究一下ButterKnife是怎么實(shí)現(xiàn)的。怎么做出來 。

五、ButterKnife封神之路

(5)原理是什么?

1.從開始調(diào)用的方法說起,ButterKnife都是從bind()開始執(zhí)行。先看下ButterKnife里面的bind方法,再來說說bind的作用

  // butterKnife 有許多bind重載方法 最終都會(huì)傳遞到 bind(@NonNull Object target, @NonNull View source)方法中

  //綁定Actvity  
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    //根據(jù)targert獲取Activity的根視圖 
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }
    //綁定視圖
  @NonNull @UiThread
  public static Unbinder bind(@NonNull View target) {
    return bind(target, target);
  }
    //綁定Dialog
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Dialog target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }
    //傳入的對象與Activity視圖綁定一起
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Activity source) {
    View sourceView = source.getWindow().getDecorView();
    return bind(target, sourceView);
  }
    //傳入的對象與Dialog視圖綁定一起
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
    View sourceView = source.getWindow().getDecorView();
    return bind(target, sourceView);
  }

//調(diào)用viewBinding構(gòu)造函數(shù)傳入綁定對象與視圖最終獲取Unbinder對象
 @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    //獲取綁定對象的類
    Class<?> targetClass = target.getClass();
    //打印類名
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //通過綁定類獲取對應(yīng)的viewBinding類的構(gòu)造函數(shù)
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    //判斷為空時(shí)返回 Unbinder的空實(shí)例
    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      // 通過反射調(diào)用了viewBinding的構(gòu)造函數(shù)創(chuàng)建了一個(gè)實(shí)例
      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);
    }
  }

——通過bind()方法傳入一個(gè)對象與視圖最終獲取Unbinder的一個(gè)實(shí)例。那Unbinder是什么,用來干嘛呢。我們看下源碼

public interface Unbinder {
  @UiThread void unbind();

  Unbinder EMPTY = () -> { };
}

——Unbinder其實(shí)是個(gè)接口,里面有個(gè)unbind方法。就是用來在Fragment中進(jìn)行解綁時(shí)用的。還有個(gè)空實(shí)現(xiàn),java 8寫法。通過上面的注釋,在未找到對應(yīng)的ViewBinding構(gòu)造函數(shù)時(shí)調(diào)用。

2.bind()方法中有個(gè)重要的方法,通過它獲取ViewBinding的構(gòu)造函數(shù)。從而實(shí)現(xiàn)對ViewBinding類的調(diào)用。源碼如下

 @VisibleForTesting
  static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

//通過綁定的類 查找對應(yīng)的ViewBinding類 并獲取ViewBinding類的構(gòu)造函數(shù)
  @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //ButterKnife定義了一個(gè)LinkedHashMap用來存儲(chǔ)綁定類和對應(yīng)的viewBinding構(gòu)造函數(shù)
    //根據(jù)指定類查找對應(yīng)的構(gòu)造函數(shù)
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    //構(gòu)造函數(shù)不為空或者存儲(chǔ)對象的key含有這個(gè)類則返回構(gòu)造函數(shù) 
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    //未得到構(gòu)造函數(shù)后 繼續(xù)向下執(zhí)行 通過反射獲取類名
    String clsName = cls.getName();
    //判斷是不是框架類 是則返回null
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      //通過類加載器 獲取 ?_viewBinding類 (這個(gè)viewBind類是在項(xiàng)目編譯ButterKnife通過APT根據(jù)生成的 命名是根據(jù)綁定的類名+ViewBinding)
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      //通過反射獲取?_ViewBinding類的構(gòu)造方法
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      //如果未發(fā)現(xiàn)?_ViewBinding類 則獲取父類傳到該方法里面繼續(xù)尋找_ViewBinding
      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);
    }
    // 把?_ViewBinding的構(gòu)造函數(shù)存儲(chǔ)在LinkedHashMap中
    BINDINGS.put(cls, bindingCtor);
    // 并返回?_ViewBinding構(gòu)造函數(shù)
    return bindingCtor;
  }

   //butterKnife其他的方法
  //設(shè)置是否打印日志
  public static void setDebug(boolean debug) {
    ButterKnife.debug = debug;
  }

——通過綁定的類利用反射得到類的加載方法,根據(jù)指定的名字去查找對應(yīng)的ViewBinding類。

——通過獲取到的ViewBinding類利用反射得到它的一個(gè)構(gòu)造函數(shù),最后通過調(diào)用構(gòu)造函數(shù)實(shí)例化ViewBinding的一個(gè)實(shí)現(xiàn)。

通過上面注釋,我們可以看到幾個(gè)方法都是通過反射機(jī)制實(shí)現(xiàn)的。那么什么是反射呢? 這里簡單介紹一下:

反射機(jī)制:反射機(jī)制允許程序在執(zhí)行期借助于Reflection API取得任何類的內(nèi)部信息,并能直接操作任意對象的內(nèi)部屬性及方法

優(yōu)點(diǎn):可以實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對象和編譯,體現(xiàn)出很大的靈活性

缺點(diǎn):對性能有影響,此類操作總是慢于直接執(zhí)行相同的操作

總結(jié):

  1. 通過bind()方法傳入一個(gè)對象與視圖
  2. 通過傳入的綁定對象利用反射原理找到對應(yīng)的viewBinding類并獲取到構(gòu)造函數(shù)
  3. 構(gòu)造函數(shù)通過反射實(shí)例化了ViewBinding類并把綁定的對象與視圖傳到ViewBinding類中

那么問題來了,綁定對象的ViewBinding類是怎么來的呢?(如何生成的?)。這里在簡單介紹一下。

首先得了解這幾個(gè)知識(shí)

APT(Android注解處理器):APT(Annotation Processing Tool) 即注解處理器,是一種注解處理工具,用來在編譯期掃描和處理注解,通過注解來生成 Java 文件。即以注解作為橋梁,通過預(yù)先規(guī)定好的代碼生成規(guī)則來自動(dòng)生成 Java 文件

原理:在注解了某些代碼元素(如字段、函數(shù)、類等)后,在編譯時(shí)編譯器會(huì)檢查 AbstractProcessor 的子類,并且自動(dòng)調(diào)用其 process() 方法,然后將添加了指定注解的所有代碼元素作為參數(shù)傳遞給該方法,開發(fā)者再根據(jù)注解元素獲取相應(yīng)的對象信息。根據(jù)這些信息通過 javapoet 生成我們所需要的代碼。

通過上面描述總結(jié):

  1. 當(dāng)編譯代碼的時(shí)候,APThi掃描和處理注解
  2. 把所有的注解傳遞到AbstractProcessor 子類的process()方法中(也就是我們需要自定義一個(gè)類繼承AbstractProcessor 類并重寫process()方法
  3. 在process()中獲取注解信息并使用javapoet技術(shù)生成我們需要的文件

那么可能有人會(huì)問什么是注解呢:

java注解又稱 Java 標(biāo)注,是 JDK5.0 引入的一種注釋機(jī)制。Java 語言中的類、方法、變量、參數(shù)和包等都可以被標(biāo)注。和 Javadoc 不同,Java 標(biāo)注可以通過反射獲取標(biāo)注內(nèi)容。在編譯器生成類文件時(shí),標(biāo)注可以被嵌入到字節(jié)碼中。Java 虛擬機(jī)可以保留標(biāo)注內(nèi)容,在運(yùn)行時(shí)可以獲取到標(biāo)注內(nèi)容。

看起來有點(diǎn)抽象:不是很了解的這里給大家在推薦個(gè) 注解視頻資源

JavaPoetJavaPoet是一個(gè)用于生成. Java源文件的Java API。

如果大家理解了這幾個(gè)知識(shí)再來看這篇文章就輕松多了,一眼掃過,就會(huì)發(fā)現(xiàn)都是廢話。作者還挺啰嗦的有木有,覺得是待會(huì)點(diǎn)個(gè)贊讓作者知道下。

寫太多篇幅太長,看得也累。所以具體如何生成的還是要靠大家去看。

繼續(xù)分析生成的ViewBinding類,這里以MainActivity為例

3.MainActivity_ViewBinding源碼及注釋如下

//1.ButterKnife類最終執(zhí)行constructor.newInstance(target, source)通過反射調(diào)用了viewBinding構(gòu)造函數(shù)方法,這里以MainActivity的ViewBinding的源碼進(jìn)行分析
@UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    //把綁定對象賦給viewBinding類的屬性,在解綁的時(shí)候釋放資源
    this.target = target;

    View view;
    // 這里調(diào)用了Utils.findRequiredViewAsType()方法來獲取控件
    target.tvHellWorld = Utils.findRequiredViewAsType(source, R.id.tv_hell_world, "field 'tvHellWorld'", TextView.class);
    target.etInput = Utils.findRequiredViewAsType(source, R.id.et_input, "field 'etInput'", EditText.class);
    //通過@OnClick注釋得到id 找到對應(yīng)的View 并實(shí)現(xiàn)點(diǎn)擊事件 并執(zhí)行MainActivity的onViewClicked方法(onViewClicked是在MainActivity中我們自己定義的名字) 
    view = source.findViewById(R.id.btn_send);
    if (view != null) {
      view7f070024 = view;
      view.setOnClickListener(new DebouncingOnClickListener() {
        @Override
        public void doClick(View p0) {
          target.onViewClicked(p0);
        }
      });
    }
    //獲取資源對象賦值給MainActivity對象
    Context context = source.getContext();
    Resources res = context.getResources();
    target.colorAccent = ContextCompat.getColor(context, R.color.colorAccent);
    target.helloWorld = res.getString(R.string.helloWorld);
  }

  //2.獲取控件的方法
  public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
    //根據(jù)id找到對應(yīng)的View
    View view = findRequiredView(source, id, who);
    //把veiw類型強(qiáng)轉(zhuǎn)成傳進(jìn)來的cls
    return castView(view, id, who, cls);
  }

   //3.內(nèi)部通過findViewById 獲取到View
   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.");
    }

    //4.把View轉(zhuǎn)成對應(yīng)的Class類型
    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);
        }
    }

// ViewBinding 實(shí)現(xiàn)Unbinder接口的unBind方法 在對象銷毀的時(shí)候把對應(yīng)的ViewBinding類里面的對象進(jìn)行釋放
  @Override
  @CallSuper
  public void unbind() {
    //釋放ViewBinding中的對象 
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.tvHellWorld = null;
    target.etInput = null;

    if (view7f070024 != null) {
      view7f070024.setOnClickListener(null);
      view7f070024 = null;
    }

  }

上面的代碼都是通過注解獲取來的信息再根據(jù)JavaPoet按照butterKnife定的規(guī)則生成的。

——通過viewBinding構(gòu)造函數(shù)獲取到控件、資源和點(diǎn)擊事件。賦值給bind()傳遞過來的綁定對象,實(shí)現(xiàn)控件/資源/點(diǎn)擊事件的綁定。

——viewBinding類實(shí)現(xiàn)Unbinder接口的unbind()方法。

通過源碼發(fā)現(xiàn)這里釋放掉了ViewBinding類的target對象和綁定對象的一些屬性與監(jiān)聽。Fragment的實(shí)現(xiàn)也基本一樣。

那么這里有個(gè)問題,為什么在Fragment中需要進(jìn)行解綁。而在其他對象中不需要解綁?

ButterKnife文檔中指出:Fragment具有不同于Activity的生命周期。在onCreateView中綁定一個(gè)Fragment時(shí),在onDestroyView中將Fragment設(shè)置為null。

可據(jù)源碼來看解綁顯然是把Fragment的一些屬性都清空,并且把對應(yīng)ViewBinding類引用Fragment對象也清空。

如果不進(jìn)行解綁Fragment ->onDestrory的時(shí)候這些就不被回收了嗎?

這也是唯一疑惑的一個(gè)地方?也許是我對Fragment生命周期仍存在一些不了解地方,很遺憾沒有在其他博客和文檔中找到我想要的答案。所以在這里提出自己的疑惑。 若有哪位朋友看到,請留言指教一二 。

注:源碼就說到這里了,看了別人分析10次源碼。不如自己看一次源碼的效果好。這是后面自己看源碼體會(huì)最深的。懂得了原理,使用起來就更得心應(yīng)手了。

總結(jié):

  1. 項(xiàng)目編譯的時(shí)候,ButterKnife使用APT根據(jù)注解獲取到的信息采用JavaPoet生成ViewBinding類。
  2. 我們通過ButterKnife.bind()方法傳入一個(gè)對象與視圖
  3. 通過傳入的綁定對象利用反射原理找到對應(yīng)的viewBinding類并獲取到構(gòu)造函數(shù)
  4. 構(gòu)造函數(shù)通過反射實(shí)例化了ViewBinding類并把綁定的對象與視圖傳到ViewBinding類中
  5. 在ViewBinding類的構(gòu)造函數(shù)中分別獲取到控件與資源再賦值給傳遞過來的綁定對象(屬性與對象綁在一起)

(6)輔助插件

這里推薦一個(gè)ButterKnife 輔助插件:Android Butterknife Zelezny (可以自動(dòng)幫生成所需要綁定的控件)

1.添加插件并重啟(這一步就不演示了)

2.右擊布局選擇Generate...

3.選擇需要綁定的控件和點(diǎn)擊監(jiān)聽或修改要生成的控件名

六、總結(jié)

放棄:

(1)在Goole的大力支持下 ,越來越多人采用kotlin開發(fā),而kotlin有更簡便的方式替代了findViewById()方法。使得ButterKnife的作用少了一些,但不是完全沒作用。ButterKnife仍可以綁定資源與方法。

(2)DataBinding:使用數(shù)據(jù)綁定,也可以代替ButterKnife。是不是完全替代就看到家怎么使用了。

或許還有許多我不知道方式。這里就舉個(gè)例子,也不補(bǔ)充了。

相關(guān)鏈接:

  1. ButterKnife文檔
  2. ButterKnife GitHb地址
  3. 反射機(jī)制
  4. APT(Android注解處理器)
  5. java注解
  6. 注解視頻資源
  7. JavaPoet

七、內(nèi)容推薦

《CSDN》
《Android 10文檔閱讀總結(jié)》
《Android 學(xué)習(xí)資源收集》
《Android 自定義控件基礎(chǔ)》
《Android Rxjava+Retrofit網(wǎng)絡(luò)請求框架封裝(一)》

八、項(xiàng)目參考

自己整理的一個(gè)工具演示項(xiàng)目,有興趣可以看下

Github:https://github.com/DayorNight/BLCS

apk下載體驗(yàn)地址:https://www.pgyer.com/BLCS

若您發(fā)現(xiàn)文章中存在錯(cuò)誤或不足的地方,希望您能指出!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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