思維導圖
使用方法
最新版本具體信息根據(jù)ButterKnife的官網(wǎng)來進行查找猾愿。
-
導入包既们。在
app
下的build.gradle
的dependencies
中進行引入,當然高版本也容易出現(xiàn)問題。
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
- 項目中進行使用迫悠。根據(jù)官網(wǎng)中給出的使用方式來使用即可,下方只給出一種使用
class ExampleActivity extends Activity {
// 通過BindView的一個
@BindView(R.id.title) TextView title;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
}
}
通過上述的@BindView
的一個注解,將布局中一個控件和引用進行相關聯(lián)的綁定操作全蝶。這樣的操作還有很多
ButterKnife中的注解 | 對應Java代碼 |
---|---|
@BindView | findViewById() |
@BindString | getResources().getString() |
@OnClick | view.setOnClickListener(new View.OnClickListener() {...}) |
不得不承認,ButterKnife
在一定的程度上會提高我的開發(fā)效率,但是他到底是怎么運作呢抑淫?
源碼分析
在使用ButterKnife
的時候其實我們是否注意到一個問題绷落,我們一定需要寫一個這樣的一段代碼。
ButterKnife.bind(this);
如果不寫會出現(xiàn)下方這樣的錯誤始苇。
@BindView(R.id.view) View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 判斷寫和不寫時的區(qū)別
// ButterKnife.bind(this);
}
@Override
protected void onResume() {
super.onResume();
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
我們能夠發(fā)現(xiàn)沒有加入這句的話的代碼出現(xiàn)對象為空的情況砌烁,那我們也就能明白ButterKnife
的入口其實就是我們必須要寫的這一段代碼了。
對ButterKnife.bind(this)
進行追溯
public static Unbinder bind(@NonNull Activity target) {
// DecoView是Window中一個變量催式,是根布局視圖的載體
// 詳細需要查看Window的唯一子類PhoneWindow
// Activity和Window綁定函喉,獲取當前的根視圖
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView); // 1
}
// 由注釋1調(diào)用的函數(shù)
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
// 去尋找一個構造函數(shù)
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); // 2
if (constructor == null) {
// 直接返回為空
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source); // 3
} catch (IllegalAccessException e) {
// 一些錯誤處理
}
}
先經(jīng)過上述代碼中的注釋2,也就是使去構造一個對象荣月。如果沒有找到管呵,就直接返回為空;如果找到構造方法了哺窄,就進行構造(使用的ClassLoader來加載捐下,也就是反射機制)。那么主要任務還是注釋3通過newInstance
函數(shù)來完成一個Unbinder
對象的創(chuàng)建萌业。
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (serializationClass == null) {
return newInstance0(initargs);
} else {
return (T) newInstanceFromSerialization(serializationCtor, serializationClass);
}
}
這里的返回值竟然是一個泛型坷襟,說明我們之前有說落了什么?回頭看看生年,其實我們就知道了Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
這段函數(shù)中傳入的泛型正是繼承自Unbinder
的啤握,所以我們的泛型返回值也就確定了。
加載文件長相
看看我們通過這個ButterKnife
生成的代碼是長什么樣的晶框。
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
// 綁定排抬,這里存在兩個函數(shù)是不是似曾相識呢?
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
// 通過變量來調(diào)用他內(nèi)部的一個變量
target.view = Utils.findRequiredView(source, R.id.view, "field 'view'");
}
// 解綁
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.view = null;
}
}
在這里其實我們已經(jīng)明白了授段,為什么我們的變量只能是public
或者不加修飾符的原因了蹲蒲。
但是我們并不只是來看這個的,我們要知道注釋的功能是如何實現(xiàn)的侵贵?我們看到了一個我們定義的view
變量届搁,做了一個Utils.findRequiredView(source, R.id.view, "field '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);
}
原來他的尋找原理還是通過findViewById()
來完成整個的定位操作的卡睦,那ButterKnife
的神奇之處也就不再神奇了。
為了驗證我們的想法漱抓,我對@OnClick
的注解做了一個測試表锻。下方貼出ButterKnife
中給出的答案。
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
viewSource = source;
source.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.start();
}
});
}
和我們寫的方式還是一模一樣的乞娄。那我們的好奇心來了瞬逊,他是如何實現(xiàn)這個代碼的輸出的显歧,這也是我們整個ButterKnife
的核心工作了。
文件生成過程
其實在最開始的導包的時候确镊,我們就應該注意到的一個問題士骤,因為我們導入的ButterKnife
并不是只有一個庫,而我們上面的那個庫的工作明顯是一個調(diào)用的過程蕾域。那猜測一下另外一個庫的作用會不會是生成的作用呢拷肌?
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
這里我們要經(jīng)歷一個測試,他是通過ButterKinife.bind(this);
觸發(fā)的還是@BindView
這一類的注解來進行觸發(fā)的旨巷?
測試之后巨缘,能夠發(fā)現(xiàn)ButterKinife.bind(this);
刪除,對我們的使用完全沒有影響契沫,但是刪去全部的@BindView
的注解后,文件沒了N艉骸P竿颉!
這也就說明了文件生成的觸發(fā)的方式來自于了注解靶病。而注解所在的包的位置正是庫com.jakewharton:butterknife-compiler
中会通。但是到底是哪個文件呢?我們只好一個個看過去了娄周。
文件也不多涕侈,那我們可以一個個看了。首先我們要明確一個目標煤辨,當然這是我的一個猜測裳涛,他應該要對注解進行一個收集,然后再進行一個源碼的生成众辨,而且這個文件中端三,可能會出現(xiàn)幾個如下的特征:
(1)輸出的時候會出現(xiàn)一個后綴"_ViewBinding"。
(2)文件路徑應該會出現(xiàn)對我們自己的包一個名字獲取鹃彻,也就是獲取包名/getPackageName()
等獲取函數(shù)郊闯。
(3)編譯時就要調(diào)用的一個注解
在ButterKnifeProcessor
中我們發(fā)現(xiàn)了一個注解@AutoService(Processor.class)
說明了這個文件,而這個注解就是為了編譯時進行加載的蛛株,那我們也就找到了我們的目標了团赁。
-
init()
函數(shù)是他的一個入口。
@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));
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
-
process()
函數(shù)是一個執(zhí)行過程谨履,主要就是一個文件的輸出欢摄。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 調(diào)用一些parseXXX的函數(shù),來獲取注解并生成相對應的數(shù)據(jù)
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 這個文件中會出現(xiàn)相對應的文件操作方式
// 比如上述的一些猜測_ViewBinding后綴的文件創(chuàng)建
// 獲取包名等一系列操作了笋粟。
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;
}
那這里我們也就完全的解釋清楚了文件是怎么進行生成的剧浸。具體的生成過程還是需要去查看butterknife.compiler
包下的BindingSet
文件锹引。
總結
在這里就能解決我們的思考的問題了,其實文章中已經(jīng)解決了大部分的問題唆香,剩下最后一個反射的問題嫌变,在這里做一個解答。
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
// 躬它。腾啥。。冯吓。通過反射機制創(chuàng)建倘待。
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
這是ButterKnife
中一段代碼,貼出他的意思很明確组贺,其實就是為了說明會出現(xiàn)反射機制凸舵,對性能也確實有一定的消耗,但是這種消耗并不大失尖,因為他做了一個緩沖的機制啊奄,也就保障我們的性能還是能夠做到較大的緩存的。從編碼效率提高的角度來看掀潮,這種性能代價并不大菇夸。
以上就是我的學習成果,如果有什么我沒有思考到的地方或是文章內(nèi)存在錯誤仪吧,歡迎與我分享庄新。