Android 淺析 ButterKnife (二) 源碼解析
前言
Linus Benedict Torvalds : RTFSC – Read The Fucking Source Code
概括
這章將會(huì)根據(jù)由淺入深的過(guò)程淺析ButterKnife的注解方式。
工程結(jié)構(gòu)
- butterknife
- butterknife-annotations
- butterknife-compiler
可以看到工程的目錄結(jié)構(gòu)還是很清楚的
butterknife負(fù)責(zé)工程的主要邏輯調(diào)用入口。
butterknife-annotations負(fù)責(zé)所有注解的自定義文件。
butterknife-compiler負(fù)責(zé)在編譯時(shí)解析Annotations。
butterknife-annotations
這里面的文件都是由注解組成。
/**
* Bind a field to the view for the specified ID. The view will automatically be cast to the field
* type.
* <pre><code>
* {@literal @}Bind(R.id.title) TextView title;
* </code></pre>
*/
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
@IdRes int[] value();
}
例如我們最經(jīng)典的Bind方法的注解就是在這里定義的,這里通過(guò)注釋我們可以很直接知道這些注解做的是什么就不再一一解析了。
butterknife-compiler
接下來(lái)我們看編譯過(guò)程,這里是butterknife的主要部分。
所有的注解要在編譯時(shí)進(jìn)行解析,都需要自定義一個(gè)類繼承于javax.annotation.processing.AbstractProcessor,通過(guò)復(fù)寫(xiě)其中的方法來(lái)實(shí)現(xiàn)。
先來(lái)看下支持注解的類類型:
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<>();
types.add(Bind.class.getCanonicalName());
for (Class<? extends Annotation> listener : LISTENERS) {
types.add(listener.getCanonicalName());
}
types.add(BindArray.class.getCanonicalName());
types.add(BindBitmap.class.getCanonicalName());
types.add(BindBool.class.getCanonicalName());
types.add(BindColor.class.getCanonicalName());
types.add(BindDimen.class.getCanonicalName());
types.add(BindDrawable.class.getCanonicalName());
types.add(BindInt.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
types.add(Unbinder.class.getCanonicalName());
return types;
}
這里我們可以看到基本都涵蓋了我們?cè)?code>butterknife-annotations自定義的文件。
接著我們來(lái)看主要的邏輯:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//查找和解析注解
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
//循環(huán)遍歷拿出注解中的鍵值
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
//寫(xiě)進(jìn)文件,生成輔助類
bindingClass.brewJava().writeTo(filer);
}
return true;
}
這里我們看到主要的process函數(shù)里實(shí)現(xiàn)的就三個(gè)功能
1:查找和解析注解;
2:循環(huán)遍歷拿出注解中的鍵值;
3:寫(xiě)進(jìn)文件,生成輔助類。
我們先來(lái)看第一個(gè)功能,查找和解析注解:
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// Process each @Bind element.
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
parseBind(element, targetClassMap, erasedTargetNames);
}
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
}
// Process each @BindInt element.
for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceInt(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindInt.class, e);
}
}
...
}
我們拿了一個(gè)比較簡(jiǎn)單的@BindInt來(lái)分析。
private void parseResourceInt(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
...
// Verify common generated code restrictions.
hasError |= isInaccessibleViaGeneratedCode(BindInt.class, "fields", element);
hasError |= isBindingInWrongPackage(BindInt.class, element);
if (hasError) {return;}
// Assemble information on the field.
String name = element.getSimpleName().toString();
int id = element.getAnnotation(BindInt.class).value();
BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
FieldResourceBinding binding = new FieldResourceBinding(id, name, "getInteger", false);
bindingClass.addResource(binding);
erasedTargetNames.add(enclosingElement);
}
這里我們首先看到isInaccessibleViaGeneratedCode(),它里面判斷了三個(gè)點(diǎn):
- 驗(yàn)證方法修飾符不能為
private和static。 - 驗(yàn)證包含類型不能為非
Class。 - 驗(yàn)證包含類的可見(jiàn)性并不是
private。
接著我們來(lái)看isBindingInWrongPackage,它判斷了這個(gè)類的包名,包名不能以android.和java.開(kāi)頭,butterknife不可以在Android Framework和JDK框架內(nèi)部使用。
最后就是調(diào)用getOrCreateTargetClass函數(shù)獲取或者生成一個(gè)綁定類,并將之存入數(shù)組。
接下來(lái)我們肯定要分析最常用的注解@Bind了:
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<TypeElement> erasedTargetNames) {
// Verify common generated code restrictions.
if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
|| isBindingInWrongPackage(Bind.class, element)) {
return;
}
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.ARRAY) {
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (LIST_TYPE.equals(doubleErasure(elementType))) {
parseBindMany(element, targetClassMap, erasedTargetNames);
} else {
parseBindOne(element, targetClassMap, erasedTargetNames);
}
}
看出這里第一步也是判斷兩個(gè)isInaccessibleViaGeneratedCode()和isBindingInWrongPackage的條件,接著通過(guò)parseBindMany()來(lái)解析這個(gè)注解:
- 驗(yàn)證這個(gè)類型是一個(gè)
List還是一個(gè)array。 - 驗(yàn)證這個(gè)目標(biāo)類型是否繼承自
View。 - 在作用域里組裝信息。
- 當(dāng)然,最后將生成的
BindingClass存入數(shù)組中。
分析完findAndParseTargets我們接著回到process往下看bindingClass.brewJava()。
JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(className)
.addModifiers(PUBLIC)
.addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
if (parentViewBinder != null) {
result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
TypeVariableName.get("T")));
} else {
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
}
if (hasUnbinder()) {
result.addType(createUnbinderClass());
}
result.addMethod(createBindMethod());
return JavaFile.builder(classPackage, result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
這個(gè)函數(shù)通過(guò)將我們綁定類的信息寫(xiě)入到文件外還負(fù)責(zé)創(chuàng)建綁定和創(chuàng)建解除綁定。
在將綁定函數(shù)寫(xiě)入到文件后,整個(gè)編譯器的注解方法就結(jié)束了。接下來(lái)就到運(yùn)行時(shí)的注解過(guò)程了。
butterknife
Field and method binding for Android views. Use this class to simplify finding views and attaching listeners by binding them with annotations.
這是關(guān)于ButterKnife類的說(shuō)明,關(guān)于ButterKnife的使用基本都是通過(guò)這個(gè)主類來(lái)實(shí)現(xiàn)的。
這里面最關(guān)鍵的函數(shù)就數(shù)bind()了:
public static void bind(@NonNull Activity target) {
bind(target, target, Finder.ACTIVITY);
}
每個(gè)bind函數(shù)實(shí)際上都是通過(guò)相同的函數(shù)不同參數(shù)實(shí)現(xiàn)。
static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
Class<?> targetClass = target.getClass();
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
viewBinder.bind(finder, target, source);
}
首先通過(guò)findViewBinderForClass生成每個(gè)類,然后再調(diào)用這些類的bind方法進(jìn)行綁定,這些類的方法都是在編譯期通過(guò)createBindMethod()方法一個(gè)個(gè)生成的。這些也就是輔助類的用處了。
總結(jié)
以上就是對(duì)ButterKnife的淺析,具體來(lái)說(shuō)就是在你寫(xiě)下注解后,ButterKnife會(huì)幫你在編譯期幫你把代碼補(bǔ)全,然后在運(yùn)行期來(lái)調(diào)用。