ButterKnife想必每一個(gè)Android開發(fā)者都或多或少使用過屋剑,它的功能強(qiáng)大之處就不用多說了扎阶。它的原理可以簡要概括為:編譯時(shí)注解(AbstractProcessor)+反射议慰,網(wǎng)上已經(jīng)有很多ButterKnife源碼解析相關(guān)的文章了仇轻,閑暇之余將ButterKnife工程clone下來又翻了遍源碼水由,當(dāng)作學(xué)習(xí)筆記整理下扬绪。
ButterKnife使用了編譯時(shí)注解竖独,入口就是ButterKnifeProcessor這個(gè)類,它繼承自AbstractProcessor挤牛,在跟進(jìn)去ButterKnifeProcessor源碼前莹痢,我們先簡要概括下注解處理器的概念:
注解處理器(AbstractProcessor)是用來在編譯過程中掃描和處理注解的工具,我們在項(xiàng)目中可以為特定的注解注冊自己的注解處理器墓赴,生成.java 文件竞膳,但不能修改已經(jīng)存在的Java類(即不能向已有的類中添加方法)。而這些生成的Java文件竣蹦,會同時(shí)與其他普通的手寫Java源代碼一起被javac編譯顶猜。若大家有對AbstractProcessor還不太了解的童鞋,請先移步至相關(guān)文章痘括。
好了长窄,我們跟進(jìn)去ButterKnifeProcessor首先先看下它的getSupportedAnnotationTypes方法
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
//ButterKnife支持的注解類型
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindAnim.class);
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(BindFont.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
可以看到ButterKnife支持的注解很多,它不但支持我們常用的BindView纲菌、OnClick注解挠日,還支持BindColor、BindDrawable等注解翰舌。接著我們跟進(jìn)去ButterKnifeProcessor的process方法看下:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//1嚣潜、調(diào)用findAndParseTargets方法,處理所有的@BindXX注解
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//2椅贱、遍歷bindingMap懂算,生成相應(yīng)的Java文件
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
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;
}
關(guān)鍵部分我在上述代碼中已經(jīng)做了標(biāo)注只冻,我們接著跟進(jìn)去findAndParseTargets方法,在findAndParseTargets方法中處理了所有支持的注解计技,由于該方法有點(diǎn)長喜德,這里我們只關(guān)注下BindView相關(guān)的部分,其他注解原理類似:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
...
// Process each @BindView element.
//1垮媒、處理每個(gè)被@BindView標(biāo)注的元素
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);
}
}
...
//2舍悯、調(diào)用BindingSet.Builder的build方法,生成相應(yīng)的BindingSet對象睡雇,并put到bindingMap中
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();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingInformationProvider parentBinding = bindingMap.get(parentType);
if (parentBinding == null) {
parentBinding = classpathBindings.get(parentType);
}
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
entries.addLast(entry);
}
}
}
//3萌衬、最后將bindingMap return掉
return bindingMap;
}
我們接著跟進(jìn)去1處的parseBindView方法:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
一系列校驗(yàn)工作
···
if (hasError) {
return;
}
//1、通過getAnnotation.value()方法它抱,獲取到相應(yīng)控件id秕豫,類似于R.id.btn_test
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
//2、將int類型的id包裝成Id對象
Id resourceId = elementToId(element, BindView.class, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(resourceId);
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 {
//3抗愁、創(chuàng)建TypeElement相對應(yīng)的BindingSet.Builder實(shí)例馁蒂,并put到builderMap中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
這里我們關(guān)注下3處呵晚,在調(diào)用getOrCreateBindingBuilder方法創(chuàng)建BindingSet.Builder對象的過程中會聲明編譯生成的.java文件名蜘腌,相見getBindingClassName方法:
static ClassName getBindingClassName(TypeElement typeElement) {
String packageName = getPackage(typeElement).getQualifiedName().toString();
String className = typeElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
return ClassName.get(packageName, className + "_ViewBinding");
}
上述代碼就印證了通過ButterKnife生成的Java文件名為className + "_ViewBinding"的形式。
好了饵隙,我們回到最初的ButterKnifeProcessor撮珠,接著看下process方法的2處,遍歷bindingMap金矛,生成相應(yīng)的Java文件芯急,這里生成Java文件用到了開源庫javapoet。我們跟進(jìn)去brewJava方法看下:
JavaFile brewJava(int sdk, boolean debuggable) {
//1驶俊、createType方法
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
跟進(jìn)去createType方法:
private TypeSpec createType(int sdk, boolean debuggable) {
//1娶耍、聲明Java文件的類名、修飾符饼酿、是否final等榕酒,其實(shí)就是開源庫javapoet的API操作
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC)
.addOriginatingElement(enclosingElement);
if (isFinal) {
result.addModifiers(FINAL);
}
//重點(diǎn):createBindingConstructor 創(chuàng)建構(gòu)造方法
result.addMethod(createBindingConstructor(sdk, debuggable));
...
return result.build();
}
createBindingConstructor方法中又調(diào)用到了addViewBinding方法,我們跟進(jìn)去看下:
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
// Optimize the common case where there's a single binding directly to a field.
FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
//標(biāo)記是否需要強(qiáng)轉(zhuǎn)
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
builder.add("($T) ", fieldBinding.getType());
}
//直接拼接findViewById($L) 故俐,$L為占位符想鹰,findViewById括號中正好為相應(yīng)控件id,類似于R.id.btn_test
builder.add("source.findViewById($L)", binding.getId().code);
} else {
//通過Utils類包裝了一層药版,內(nèi)部也是調(diào)用findViewById操作
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;
}
...
}
方法addViewBinding中簡單明了辑舷,就是用來拼接我們在Activity或者Fragment中常寫的findViewById那行代碼。
到這里AbstractProcessor相關(guān)的部分我們就分析完畢了槽片,簡單講就是在代碼編譯期間掃描每個(gè)Java文件中的特定的注解何缓,通過開源庫javapoet來“拼湊”成ViewBinding文件肢础,該Java文件的命名為className + "_ViewBinding"。那么那么中間文件又是在什么時(shí)候被調(diào)用的呢碌廓?答案就是ButterKnife.bind(this);
我們跟進(jìn)去ButterKnife.bind方法看下:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
接著跟進(jìn)去bind方法:
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
//1乔妈、獲取目標(biāo)class
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
//2、通過反射獲取對應(yīng)_viewBinding類的構(gòu)造方法
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
//3氓皱、創(chuàng)建構(gòu)造對象路召,執(zhí)行findViewById操作(在編譯時(shí),我們在構(gòu)造方法中“拼接”了findViewById代碼)
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);
}
}
好了波材,ButterKnife源碼分析到這里就結(jié)束了股淡,下一小節(jié)我們手動擼一個(gè)簡易的ButterKnife框架。