博文出處:ButterKnife源碼分析,歡迎大家關注我的博客,謝謝!
0x01 前言
在程序開發的過程中,總會有一些場景需要去寫重復冗余的代碼。而程序員一般都是懶惰了(懶惰促使人進步 ο ),所以就出現了很多可以減少重復工作的框架或者工具。比如今天要分析的主角—— ButterKnife ,如果你做 Android 開發卻沒有聽說過 ButterKnife 那就 Out 啦。ButterKnife 使用依賴注入的方式來減少程序員去編寫一堆 findViewById
的代碼,使用起來很方便。那么接下來就一步步地帶你深入理解 ButterKnife 框架。PS:最近寫的博客篇幅都有點長,請耐心閱讀!Logo 圖鎮樓!
0x02 ButterKnife 的使用方法
我們先講下 ButterKnife 的使用方法:
-
在
app/build.gradle
中添加依賴:dependencies { compile 'com.jakewharton:butterknife:8.4.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' }
-
在
Activity
中添加注解:public class ExampleActivity extends Activity { @BindView(R.id.user) EditText username; @BindView(R.id.pass) EditText password; @OnClick(R.id.submit) public void onClick(View v) { // TODO onClick View... } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // TODO Use fields... } }
使用方法非常簡單,不得不贊嘆 ButterKnife 實在是太方便了。徹底跟 findViewById
say goodbye 啦。但是我們也認識到,如果一個框架使用起來越簡單,那么這個框架內部做的事情就越多。所以在 ButterKnife 內部一定做了很多事情。
今天我們主要分析下 ButterKnife 的三個部分:Annotation 、ButterKnifeProcessor 和 ButterKnife 。這三個部分就把整個 View 依賴注入的原理串聯起來了。
準備好了嗎?下面我們就一探究竟。(PS:本文分析的 ButterKnife 源碼為 8.4.0 版本)
0x03 Annotation
我們先來看一下其中的注解部分。ButterKnife 的注解都在 butterknife-annotations 模塊下:
發現我們平時常用的 @BindView
、@OnClick
和 @OnItemClick
都在里面。我們就挑 @BindView
(路徑:butterknife-annotations/butterknife/BindView.java) 來看一下:
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
注解都是用 @interface
來表示。在 BindView 注解的上面還有 @Retention
和 @Target
。
-
@Retention
:表示注解的保留時間,可選值 SOURCE(源碼時),CLASS(編譯時),RUNTIME(運行時),默認為 CLASS ; -
@Target
:表示可以用來修飾哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標注則表示可修飾所有。
所以我們可知,@BindView
是用來修飾 field 的,并且保留至編譯時刻。內部有一個默認屬性 value
,用來表示 View 的 id ,即平時程序中的 R.id.xxx
。
0x04 ButterKnifeProcessor
如果只有 @BindView
是不行的,我們還需要去解析注解。如何去解析編譯時的注解呢?我們可以創建一個繼承自 AbstractProcessor
的注解處理器,然后實現相關方法。在 ButterKnife 中 ButterKnifeProcessor
(路徑:butterknife-compiler/butterknife/compiler/ButterKnifeProcessor.java) 就是用來解析這些注解的注解處理器。
init(ProcessingEnvironment env)
我們先來看看 ButterKnifeProcessor
中的 init(ProcessingEnvironment env)
方法:
@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.");
}
}
// 得到一些有用的工具類
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
在 init
中主要根據 env
得到一些工具類。其中的 filter
主要是用來生成 Java 代碼,而 elementUtils
和 typeUtils
會在下面源碼中用到。
getSupportedAnnotationTypes()
private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(//
OnCheckedChanged.class, //
OnClick.class, //
OnEditorAction.class, //
OnFocusChange.class, //
OnItemClick.class, //
OnItemLongClick.class, //
OnItemSelected.class, //
OnLongClick.class, //
OnPageChange.class, //
OnTextChanged.class, //
OnTouch.class //
);
@Override public Set<String> getSupportedAnnotationTypes() {
// 返回注解處理器支持處理的注解
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
// 得到所有的注解
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
getSupportedAnnotationTypes()
方法的作用就是返回該注解處理器所支持處理的注解集合。在 getSupportedAnnotations()
中我們可以看到一些熟悉的注解,比如 @BindView
、@OnClick
和 @OnItemClick
等。
process(Set<? extends TypeElement> elements, RoundEnvironment env)
接下來就是重頭戲了,注解處理器中最重要的方法 process(Set<? extends TypeElement> elements, RoundEnvironment env)
。process(Set<? extends TypeElement> elements, RoundEnvironment env)
的代碼看上去沒幾行,其實大部分都寫在其他私有方法中了:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 掃描所有注解,最后生成 map
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
// 遍歷 bindingMap 并且通過 Filer 生成 Java 代碼
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return true;
}
總體來看 process
方法就干了兩件事情:
- 掃描所有的注解,然后生成以
TypeElement
為 key ,BindingSet
為 value 的 Map ; - 根據生成的 Map ,遍歷后通過 Filter 來生成對應的輔助類源碼。PS:ButterKnife 使用了 JavaPoet 來生成 Java 源碼。如果對 JavaPoet 不太熟悉,可以先閱讀這篇文章 《javapoet——讓你從重復無聊的代碼中解放出來》 。
我們慢慢來看,先來分析一下 findAndParseTargets(env)
:
// 掃描所有的ButterKnife注解,并且生成以TypeElement為鍵,BindingSet為值的HashMap
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
// 省略一堆解析各種注解的源碼,這些源碼做的事情和下面這個 for 循環一樣
// 所以只要看這個解析 @BindView 就夠了
...
// Process each @BindView element.
// 遍歷所有被 @BindView 標注的元素
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
...
}
先來看關于 BindView
的那個 for 循環,它會遍歷所有被 @BindView
注解的屬性,然后調用 parseBindView
方法。那么我們就先看到 findAndParseTargets
的前半段,一起跟進 parseBindView
的方法中去。
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
// 得到注解 @BindView 元素所在的類元素
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
// ---------- 類型校驗邏輯 start ---------------
// 判斷是否被注解在屬性上,如果該屬性是被 private 或者 static 修飾的,則出錯
// 判斷是否被注解在錯誤的包中,若包名以“android”或者“java”開頭,則出錯
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
// 判斷元素是不是View及其子類或者Interface
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
note(element, "@%s field with unresolved type (%s) "
+ "must elsewhere be generated as a View or interface. (%s.%s)",
BindView.class.getSimpleName(), elementType, enclosingElement.getQualifiedName(),
element.getSimpleName());
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
}
// 如果有錯誤 不執行下面代碼
if (hasError) {
return;
}
//---------------- 類型校驗邏輯 end -----------------
// Assemble information on the field. //得到被注解的注解值,即 R.id.xxx
int id = element.getAnnotation(BindView.class).value();
// 根據所在的類元素去查找 builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
// 如果相應的 builder 已經存在
if (builder != null) {
// 得到相對應的 View 綁定的屬性名
String existingBindingName = builder.findExistingBindingName(getId(id));
// 若該屬性名已經存在,則說明之前已經綁定過,會報錯
if (existingBindingName != null) {
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBindingName,
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
// 如果沒有對應的 builder ,就通過 getOrCreateBindingBuilder 方法生成,并且放入 builderMap 中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
// 得到注解名
String name = element.getSimpleName().toString();
// 得到注解元素的類型
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
// 根據 id ,添加相對應的 Field 的綁定信息
builder.addField(getId(id), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
// 添加到待 unbind 的序列中
erasedTargetNames.add(enclosingElement);
}
在 parseBindView
方法中基本上都加了注釋,在方法的開頭會對該 element
去做校驗。如果校驗沒通過的話,就沒有下面代碼的什么事了。若校驗通過之后,生成該 element
所在的類元素對應的 builder ,builder 中添加相應的 Field 綁定信息,最后添加到待 unbind 的序列中去。
現在,我們回過頭來看看 findAndParseTargets(env)
方法的后半段:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
... // 省略前半部分源碼
// Associate superclass binders with their subclass binders. This is a queue-based tree walk
// which starts at the roots (superclasses) and walks to the leafs (subclasses).
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
// 一個個取出遍歷
Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
// 得到對應的 key 和 value
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
// 找到該類元素的父元素
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
// 生成 BindingSet ,放入 Map 中
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
// 設置父元素的 BindingSet
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
// 有父元素,但是父元素的 BindingSet 還沒有被 build 出來,
// 所以再放入 entries 中等待遍歷
entries.addLast(entry);
}
}
}
// 解析結果都會存放在 bindingMap 中
return bindingMap;
}
在 findAndParseTargets(env)
方法的后半段中,主要就是把之前的 builderMap
轉換為了 bindingMap
并返回。
到了這里,我們把 process(Set<? extends TypeElement> elements, RoundEnvironment env)
做的第一件事情搞清楚了,下面就接著來看第二件事情了。
// 遍歷 bindingMap 并且通過 Filer 生成 Java 代碼
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
brewJava(int sdk)
從上面可以看到,遍歷了之前得到的 bindingMap
,然后利用 binding
中的信息生成相應的 Java 源碼。所以在 binding.brewJava(sdk)
這個方法是我們重點關注對象。那么就進入 BindingSet
(路徑:butterknife-compiler/butterknife/compiler/BindingSet.java) 這個類中去看看吧:
JavaFile brewJava(int sdk) {
// 生成 JavaFile,添加相應的注釋
return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
brewJava(int sdk)
方法的代碼竟然這么短 O_o ,就是利用了 JavaFile.builder
生成了一個 JavaFile
對象而已。但是我們發現其中有一個 createType(int sdk)
方法,隱隱約約感覺一定是這個方法在搞大事情。繼續跟進去看:
private TypeSpec createType(int sdk) {
// 生成類名為 bindingClassName 的公共類,比如 MainActivity_ViewBinding
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
// 是否修飾為 final ,默認是 false
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
// 如果有父類的話,那么要繼承父類
result.superclass(parentBinding.bindingClassName);
} else {
// 如果沒有父類,那么實現 Unbinder 接口
result.addSuperinterface(UNBINDER);
}
// 增加一個變量名為target,類型為targetTypeName的成員變量
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
// 如果沒有 View 綁定
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
// 該生成的構造方法被 @deprecated ,一般作為反射使用
result.addMethod(createBindingViewDelegateConstructor(targetTypeName));
}
// 生成構造方法,另外 findViewById 類似的代碼都在這里生成
// Xxxx_ViewBinding 一般都是執行這個方法生成構造器
result.addMethod(createBindingConstructor(targetTypeName, sdk));
if (hasViewBindings() || parentBinding == null) {
//生成unBind方法
result.addMethod(createBindingUnbindMethod(result, targetTypeName));
}
return result.build();
}
在 createType(int sdk)
方法中,基本構建好了一個類的大概,其中對于構造器以及類似 findViewById
的操作都是在 createBindingConstructor(targetTypeName, sdk)
中實現:
private MethodSpec createBindingConstructor(TypeName targetType, int sdk) {
// 創建構造方法,方法修飾符為 public ,并且添加注解為UiThread
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
// 如果有方法綁定,比如 @OnClick
if (hasMethodBindings()) {
// 如果有,那么添加 targetType 類型,final 修飾,參數名為 target 的構造方法參數
constructor.addParameter(targetType, "target", FINAL);
} else {
// 如果沒有,和上面比起來就少了一個 final 修飾符
constructor.addParameter(targetType, "target");
}
// 如果有注解的 View
if (constructorNeedsView()) {
// 那么添加 View source 參數
constructor.addParameter(VIEW, "source");
} else {
// 否則添加 Context context 參數
constructor.addParameter(CONTEXT, "context");
}
if (hasUnqualifiedResourceBindings()) {
// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "ResourceType")
.build());
}
// 如果有父類,那么會根據不同情況調用不同的 super 語句
if (parentBinding != null) {
if (parentBinding.constructorNeedsView()) {
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {
constructor.addStatement("super(target, source.getContext())");
} else {
constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}
// 如果有綁定 Field 或者方法,那么添加 this.target = target 語句
if (hasTargetField()) {
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
// 如果有 View 綁定
if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
// 為 View 綁定生成類似于 findViewById 之類的代碼
addViewBinding(constructor, binding);
}
// 為 View 的集合或者數組綁定
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render());
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}
// 綁定 resource 資源的代碼
if (!resourceBindings.isEmpty()) {
if (constructorNeedsView()) {
constructor.addStatement("$T context = source.getContext()", CONTEXT);
}
if (hasResourceBindingsNeedingResource(sdk)) {
constructor.addStatement("$T res = context.getResources()", RESOURCES);
}
for (ResourceBinding binding : resourceBindings) {
constructor.addStatement("$L", binding.render(sdk));
}
}
return constructor.build();
}
通過上面的代碼就生成了構造器,但是我們還是沒有看到具體 findViewById
操作的代碼。別急,這些代碼都在 addViewBinding(constructor, binding)
里會看到:
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
if (binding.isSingleFieldBinding()) {
// Optimize the common case where there's a single binding directly to a field.
FieldViewBinding fieldBinding = binding.getFieldBinding();
// 注意這里直接使用了 target. 的形式,所以屬性肯定是不能 private 的
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
// 下面都是 View 綁定的代碼
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!requiresCast && !fieldBinding.isRequired()) {
builder.add("source.findViewById($L)", binding.getId().code);
} else {
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
if (requiredBindings.isEmpty()) {
result.addStatement("view = source.findViewById($L)", binding.getId().code);
} else if (!binding.isBoundToRoot()) {
result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
binding.getId().code, asHumanDescription(requiredBindings));
}
addFieldBinding(result, binding);
// OnClick 等監聽事件綁定
addMethodBindings(result, binding);
}
至此,整個 ButterKnifeProcessor
解析注解、生成 Java 代碼的流程就走完了。我們來看看生成的代碼到底長成什么樣子:
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
private View view2131427413;
@UiThread
public MainActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.button, "field 'button' and method 'onClick'");
target.button = Utils.castView(view, R.id.button, "field 'button'", Button.class);
view2131427413 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick(p0);
}
});
target.tv = Utils.findRequiredViewAsType(source, R.id.tv, "field 'textView'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.button = null;
target.tv = null;
view2131427413.setOnClickListener(null);
view2131427413 = null;
this.target = null;
}
}
不得不贊嘆一句,JavaPoet 生成的代碼跟我們手寫的基本上沒什么區別。JavaPoet 實在是太強大了 *ο* 。
0x05 ButterKnife
bind()
通過之前介紹 ButterKnife 的使用方法,我們知道 View 綁定是通過調用 ButterKnife.bind()
方法來實現的。下面我們來看看其內部原理 (路徑:butterknife/butterknife/ButterKnife.java) :
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
@NonNull @UiThread
public static Unbinder bind(@NonNull View target) {
return createBinding(target, target);
}
...
createBinding(@NonNull Object target, @NonNull View source)
發現 bind()
方法內都會去調用 createBinding(@NonNull Object target, @NonNull View source)
:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
// 得到 target 的類名,比如 MainActivity
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
// 找到 target 對應的構造器
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
// 創建對應的對象
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);
}
}
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
// 對構造器的查找進行了緩存,可以直接從 Map 中獲取
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 {
// 得到對應的 class 對象,比如 MainActivity_ViewBinding
Class<?> bindingClass = Class.forName(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;
}
其實 createBinding(@NonNull Object target, @NonNull View source)
方法做的事情就是根據 target
創建對應的 targetClassName_ViewBinding
。在 targetClassName_ViewBinding
的構造器中會把對應的 View 進行綁定(具體可以查看上面的 MainActivity_ViewBinding
)。而在 findBindingConstructorForClass(Class<?> cls)
方法中也使用了 Class.forName()
反射來查找 Class
,這也是無法避免的。但是僅限于一個類的第一次查找,之后都會從 BINDINGS
緩存中獲取。
0x06 總結
總體來說,ButterKnife 是一款十分優秀的依賴注入框架,方便,高效,減少代碼量。最重要的是解放程序員的雙手,再也不用去寫無聊乏味的 findViewById
了 \(╯-╰)/ 。與 ButterKnife 原理相似的,還有 androidannotations 框架。感興趣的同學可以自己研究一下。那么,今天的 ButterKnife 解析到這里就結束了。如果對此有問題或疑惑的同學可以留言,歡迎探討。
Goodbye !~~