世界公認(rèn)最高效的學(xué)習(xí)方法:
選擇一個(gè)你要學(xué)習(xí)的內(nèi)容
想象如果你要將這些內(nèi)容教授給一名新人,該如何講解
如果過程中出了問題,重新回顧這個(gè)內(nèi)容
簡化:容你的講解越來越簡單易懂 ————理查德費(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è)問題展開:
- 是什么?
- 有什么用?
- 為什么要用?
- 怎么用?
- 原理是什么?
等明白這幾個(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é):
- 通過bind()方法傳入一個(gè)對象與視圖
- 通過傳入的綁定對象利用反射原理找到對應(yīng)的viewBinding類并獲取到構(gòu)造函數(shù)
- 構(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é):
- 當(dāng)編譯代碼的時(shí)候,APThi掃描和處理注解
- 把所有的注解傳遞到AbstractProcessor 子類的process()方法中(也就是我們需要自定義一個(gè)類繼承AbstractProcessor 類并重寫process()方法
- 在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è) 注解視頻資源
JavaPoet:JavaPoet是一個(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é):
- 項(xiàng)目編譯的時(shí)候,ButterKnife使用APT根據(jù)注解獲取到的信息采用JavaPoet生成ViewBinding類。
- 我們通過ButterKnife.bind()方法傳入一個(gè)對象與視圖
- 通過傳入的綁定對象利用反射原理找到對應(yīng)的viewBinding類并獲取到構(gòu)造函數(shù)
- 構(gòu)造函數(shù)通過反射實(shí)例化了ViewBinding類并把綁定的對象與視圖傳到ViewBinding類中
- 在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)鏈接:
七、內(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ò)誤或不足的地方,希望您能指出!