最新版本具體信息根據ButterKnife的官網來進行查找。java
app
下的build.gradle
的dependencies
中進行引入,固然高版本也容易出現問題。implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
複製代碼
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
的一個註解,將佈局中一個控件和引用進行相關聯的綁定操做。這樣的操做還有不少git
ButterKnife中的註解 | 對應Java代碼 |
---|---|
@BindView | findViewById() |
@BindString | getResources().getString() |
@OnClick | view.setOnClickListener(new View.OnClickListener() {...}) |
不得不認可,ButterKnife
在必定的程度上會提升個人開發效率,可是他究竟是怎麼運做呢?github
在使用ButterKnife
的時候其實咱們是否注意到一個問題,咱們必定須要寫一個這樣的一段代碼。緩存
ButterKnife.bind(this);
複製代碼
若是不寫會出現下方這樣的錯誤。app
@BindView(R.id.view) View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 判斷寫和不寫時的區別
// ButterKnife.bind(this);
}
@Override
protected void onResume() {
super.onResume();
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
複製代碼
咱們可以發現沒有加入這句的話的代碼出現對象爲空的狀況,那咱們也就能明白ButterKnife
的入口其實就是咱們必需要寫的這一段代碼了。ide
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調用的函數
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
// 去尋找一個構造函數
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) {
// 一些錯誤處理
}
}
複製代碼
先通過上述代碼中的註釋2,也就是使去構造一個對象。若是沒有找到,就直接返回爲空;若是找到構造方法了,就進行構造(使用的ClassLoader來加載,也就是反射機制)。那麼主要任務仍是註釋3經過newInstance
函數來完成一個Unbinder
對象的建立。函數
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);
這段函數中傳入的泛型正是繼承自Unbinder
的,因此咱們的泛型返回值也就肯定了。源碼分析
看看咱們經過這個ButterKnife
生成的代碼是長什麼樣的。佈局
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
// 綁定,這裏存在兩個函數是否是似曾相識呢?
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
// 經過變量來調用他內部的一個變量
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;
}
}
複製代碼
在這裏其實咱們已經明白了,爲何咱們的變量只能是public
或者不加修飾符的緣由了。post
可是咱們並不僅是來看這個的,咱們要知道註釋的功能是如何實現的?咱們看到了一個咱們定義的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();
}
});
}
複製代碼
和咱們寫的方式仍是如出一轍的。那咱們的好奇心來了,他是如何實現這個代碼的輸出的,這也是咱們整個ButterKnife
的核心工做了。
其實在最開始的導包的時候,咱們就應該注意到的一個問題,由於咱們導入的ButterKnife
並非只有一個庫,而咱們上面的那個庫的工做明顯是一個調用的過程。那猜想一下另一個庫的做用會不會是生成的做用呢?
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
複製代碼
這裏咱們要經歷一個測試,他是經過ButterKinife.bind(this);
觸發的仍是@BindView
這一類的註解來進行觸發的? 測試以後,可以發現ButterKinife.bind(this);
刪除,對咱們的使用徹底沒有影響,可是刪去所有的@BindView
的註解後,文件沒了!!! 這也就說明了文件生成的觸發的方式來自於了註解。而註解所在的包的位置正是庫com.jakewharton:butterknife-compiler
中。可是究竟是哪一個文件呢?咱們只好一個個看過去了。
文件也很少,那咱們能夠一個個看了。首先咱們要明確一個目標,固然這是個人一個猜想,他應該要對註解進行一個收集,而後再進行一個源碼的生成,並且這個文件中,可能會出現幾個以下的特徵: (1)輸出的時候會出現一個後綴"_ViewBinding"。 (2)文件路徑應該會出現對咱們本身的包一個名字獲取,也就是獲取包名/getPackageName()
等獲取函數。 (3)編譯時就要調用的一個註解
在ButterKnifeProcessor
中咱們發現了一個註解@AutoService(Processor.class)
說明了這個文件,而這個註解就是爲了編譯時進行加載的,那咱們也就找到了咱們的目標了。
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));
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
複製代碼
process()
函數是一個執行過程,主要就是一個文件的輸出。@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 調用一些parseXXX的函數,來獲取註解並生成相對應的數據
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 這個文件中會出現相對應的文件操做方式
// 好比上述的一些猜想_ViewBinding後綴的文件建立
// 獲取包名等一系列操做了。
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
文件。
在這裏就能解決咱們的思考的問題了,其實文章中已經解決了大部分的問題,剩下最後一個反射的問題,在這裏作一個解答。
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;
}
// 。。。。經過反射機制建立。
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
複製代碼
這是ButterKnife
中一段代碼,貼出他的意思很明確,其實就是爲了說明會出現反射機制,對性能也確實有必定的消耗,可是這種消耗並不大,由於他作了一個緩衝的機制,也就保障咱們的性能仍是可以作到較大的緩存的。從編碼效率提升的角度來看,這種性能代價並不大。
以上就是個人學習成果,若是有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。
相關文章推薦: