1.概述。
2.@BindView解析流程。
这篇文章将接着上篇文章的findAndParseTargets(RoundEnvironment env)方法,如果你看了上篇文章,那么你就知道,所有Butterknife中注解的解析都是在这个方法中的。好的那么我们开始吧。我们这个方法的流程上篇文章已经说过了,这里就不再浪费口水了,我们把解析@BindView的代码拷出来再看一下:
// Process each @BindView element.
/**
* 处理BindView(R.id.btn)注解;
* 1.做了一系列验证;如:private static修饰符判断、@BindView必须作用在View上或者interface上等等;
* 2.id的处理;生成QualifiedId、缓存等;
* 3.builder创建;一个id是否注解了多次
* 4.@Nullable处理,创建FieldViewBinding存入builder,将enclosingElement存入erasedTargetNames;
*/
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注解了的Element,通过for()循环处理每一个,一个try{}catch{}代码块,try中出问题,打印信息,关于在注解处理器中处理错误log信息,在这里就不讲了,可以在init()方法中获取Messager辅助类。看来重点在parseBindView(element, builderMap, erasedTargetNames);方法中了,跟进去:
parseBindView(element, builderMap, erasedTargetNames);方法中了,跟进去:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
//获取父级Element;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
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();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
//@BindView必须作用在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, qualifiedName, simpleName);
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), qualifiedName, simpleName);
hasError = true;
}
}
if (hasError) {
return;
}
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
//通过enclosingElement获取builder,每一个builder对应一个类,如activity;
BindingSet.Builder builder = builderMap.get(enclosingElement);
//将element所在包与id封装到QualifiedId中;
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
//判断当前@BindView所修饰控件是否已经绑定过;
//getId():将id存入Id对象,并存入symbols;
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
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;
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
//判断是否添加了@Nullable
boolean required = isFieldRequired(element);
//通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding)
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
方法也还好,不到100行,来看看在这个方法中做了一些什么操作吧。咱们先拿出一部分来看:
//获取父级Element;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
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();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
//@BindView必须作用在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, qualifiedName, simpleName);
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), qualifiedName, simpleName);
hasError = true;
}
}
if (hasError) {
return;
}
这部分是对我们@BindView注解应用的一个正确性的一个检查,首先获取了我们的element对应的外部类的TypeElement,比如,activity中用@BindView注解了一个Button btn;获取了activity对应的TypeElement,如果你看了上篇文章你就会明白。然后调用了一个isInaccessibleViaGeneratedCode(BindView.class,"fields", element)方法与一个isBindingInWrongPackage(BindView.class, element)方法进行判断操作。一个一个来看一下,第一个:
/**
* 检查annotation作用域是否正确;
*
* @param annotationClass
* @param targetThing
* @param element
* @return
*/
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
//获取当前element所在类的TypeElement;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify method modifiers.获取当前element的修饰符;
Set<Modifier> modifiers = element.getModifiers();
//修饰符不能是private或者static的,否则报告异常(error方法);
// Messager提供给注解处理器一个报告错误、警告以及提示信息的途径。
// 它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。
if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
error(element, "@%s %s must not be private or static. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
// Verify containing type.element只能直接从属于类(不能修饰局部变量);否则报错;
if (enclosingElement.getKind() != CLASS) {
error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
// Verify containing class visibility is not private.element外层类不能是私有的,否则报错;
if (enclosingElement.getModifiers().contains(PRIVATE)) {
error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
return hasError;
}
在这个类中判断了三种非正常情况:
1.@BindView注解的Element为private或者static修饰报错,如下例子:
@BindView(R.id.btn)
private Button btn;
这种情况下就会报错。
2.当前Element不是直接在一个类里边,报错。
如:在一个方法的局部变量上添加了注解。
3.外部Element是private的报错。如:
private class Activity extends ...{
@BindView(R.id.btn)
Button btn;
}
再看一下isBindingInWrongPackage(BindView.class, element)方法:
/**
* 检查anitation注解是否作用在了系统类上;
*
* @param annotationClass
* @param element
* @return
*/
private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
//不能作用于Android系统类里;
if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
//不能作用在java系统类中;
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
return false;
}
这个方法主要是检查我们是否用到了系统类上,这里注意我们自己定义的包名。接着回到上一个方法向下看,后续又检查了是否用在了View子类上或者Interface上。我都加了注释,不在详细解释。如果上边检查有一步出问题,则return终止。到这里,检查我们应用@BindView合法性就完了。再往下边走:
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
//通过enclosingElement获取builder,每一个builder对应一个类,如activity;
BindingSet.Builder builder = builderMap.get(enclosingElement);
//将element所在包与id封装到QualifiedId中;
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
//判断当前@BindView所修饰控件是否已经绑定过;
//getId():将id存入Id对象,并存入symbols;
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
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;
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
首先获取到我们注解中指定的id,如下代码中的R.id.btn:
@BindView(R.id.btn)
Button btn;
查看builderMap中是否已经缓存了外部Element对应的BindingSet.Builder,通过id创建QualifiedId,看一下这个过程:
private QualifiedId elementToQualifiedId(Element element, int id) {
return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);
}
这里用到了辅助类elementUtils获取element的包名,通过包名与id创建了QualifiedId
后边是builder判断,如果不为空,通过QualifiedId生成Id判断是否注解过相同id,注解过,则输出错误信息。Builder为空,调用getOrCreateBindingBuilder(builderMap, enclosingElement)创建或获取builder,看一下:
private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
//生成一个builder,
// builder中保存了泛型信息、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部;
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
如果我们的builderMap中已经存在,直接返回,不存在调用BindingSet.newBuilder()创建并保存到builderMap中。创建保存这步是关键,看一下:
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
//判断类型;
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
//泛型处理;
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
String packageName = getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
//参数:泛型、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部;
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
首先判断了我们外部类Element是不是View是不是Activity是不是Dialog,进行了泛型判断处理,通过包名与类名得到我们将要创建的Java类的名称,有没有final修饰,然后通过以上判断的结果生成Builder对象返回。这样我们就创建了一个BindingSet.Builder对象,其中包含是不是View,activity,Dialog,将来要生成的Java类名,有没有final修饰,泛型信息。再次回到parseBindView()方法中看剩余部分:
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
//判断是否添加了@Nullable
boolean required = isFieldRequired(element);
//通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding)
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
获取注解的element的name(上边例子中的btn)与type(上边例子中的Button),判断了是否添加了@Nullable注解,通过这三个内容生成FieldViewBinding(与MethodViewBinding都继承了MemberViewBinding,封装一个最基础的@BindView注解的element对象,)对象,调用builder的addField()方法将Id对象与FieldViewBinding对象传入,进入BindingSet的Buidler看一下:
void addField(Id id, FieldViewBinding binding) {
getOrCreateViewBindings(id).setFieldBinding(binding);
}
又调用了getOrCreateViewBindings()方法,跟:
private ViewBinding.Builder getOrCreateViewBindings(Id id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
//创建针对当前id的ViewBinding.Builder;
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}
通过查看缓存中是否已经保存了当前id对应的ViewBinding.Builder,有返回,没有创建并缓存。在这里通过id创建了ViewBinding.Builder对象。回到addField()中,调用创建好的ViewBinding.Builder对象的setFieldBingding()方法将FieldViewBinding对象保存。这里的层次你得搞清楚了。FieldViewBinding保存在ViewBinding.Builder对象中,ViewBinding.Builder对象是在BindingSet.Builder对象调用addField()方法时创建的。回到parseBindView()方法,还剩一句代码,就是将父类的elementType保存到一开始的erasedTargetNames中。好,我们的parseBindView()方法就分析完了。同样,我们解析@BindView注解逻辑也就分析完了,你弄明白了吗,不明白也没关系,我做一下总结:
1.首先就是一系列判断我们应用@BindView注解的正确性;
2.然后获取id,获取外部类ElementType对应的BindingSet.Builder对象是否存在,存在判断当前id是否已经注解过。不存在,创建。创建的过程中获取了类是否为View、activity、Dialog,是否final,泛型,将生成的类名等。保存到builderMap中。
3.调用BindingSet.Builder对象的addField()方法,过程中创建了与之对应的FieldViewBinding对象、ViewBinding.Buidler对象,并将部分信息进行缓存。
4.将外部类ElementType保存到erasedTargetNames中。
这就是解析@ViewBind注解的整个过程。
下边说一下个人的理解,如果一个类(如Activity)里边有@ViewBind注解的控件,那么就会对应这个类生成一个BindingSet.Builder,每一个@ViewBind注解的id会对应生成一个ViewBinding.Builder对象,一个类里边的所有生成的ViewBinding.Builder对象会根据id保存到BindingSet中的viewIdMap中。ViewBinding.Builder中保存了自己的FieldViewBinding对象。最终就是把生成的ViewBinding.Builder对象与外部类的ElementType对应着保存到了builderMap中,将外部类的ElementType保存到erasedTargetNames中。好了,关于@ViewBind的解析就分析到这里。
3.@OnClick注解解析过程分析
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
下边看一下ButterKnife的事件处理流程,通过上边的代码可以看出来,所有的事件注解处理流程都是一样的,看一下for循环中方法,这里传入的是辅助对象env,当前要处理的事件注解类listener,还有就是builderMap,erasedTargetNames,我们进入findAndParseListener()方法,看一下:
/**
* 传入一种事件注解进行处理;
*
* @param env
* @param annotationClass
* @param builderMap
* @param erasedTargetNames
*/
private void findAndParseListener(RoundEnvironment env,
Class<? extends Annotation> annotationClass,
Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
//获取被某个事件注解的每一个element;
for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
//google封装的方法验证element合法性;
if (!SuperficialValidation.validateElement(element)) continue;
try {
//anotationClass是@@OnClickListener;element是被其注解的方法类型的element;
parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
} catch (Exception e) {
StringWriter stackTrace = new StringWriter();
e.printStackTrace(new PrintWriter(stackTrace));
error(element, "Unable to generate view binder for @%s.\n\n%s",
annotationClass.getSimpleName(), stackTrace.toString());
}
}
}
部分代码有注释,看一下关键代码parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);这个方法比较长,这里就不贴了,首先还是判断合法性,这部分代码不太重要。
//反射方式获取id数组;
int[] ids = (int[]) annotationValue.invoke(annotation);
...//省略部分合法行应用代码;
//生成类里边重写父类的方法;如@OnClick中的doClick();
ListenerMethod method;
//获取@ListenerClass注解内部的@ListenerMethod注解信息;
ListenerMethod[] methods = listener.method();
if (methods.length > 1) {
throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.",
annotationClass.getSimpleName()));
} else if (methods.length == 1) {
//@OnClick等都是一个;
if (listener.callbacks() != ListenerClass.NONE.class) {
throw new IllegalStateException(
String.format("Both method() and callback() defined on @%s.",
annotationClass.getSimpleName()));
}
method = methods[0];
} else {
//callback处理;@OnItemSelected、@OnPageChange、@OnTextChanged
Method annotationCallback = annotationClass.getDeclaredMethod("callback");
Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
Field callbackField = callback.getDeclaringClass().getField(callback.name());
method = callbackField.getAnnotation(ListenerMethod.class);
if (method == null) {
throw new IllegalStateException(
String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
callback.name()));
}
}
上边代码主要是获取了在定义事件注解时定义的方法信息,这里看一下我们的@OnClick注解是怎么定义的:
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
@IdRes int[] value() default { View.NO_ID };
}
以上代码主要是获取的这个method就是@OnClick注解类中的@ListenerClass 中的@ListenerMethod中的doClick。接着去代码里找关键代码:
/**
* 被注解事件注解的方法参数与jack在注解事件中定义的方法参数做匹配处理;如simple中的onItemClick()参数,如下代码;
* @OnItemClick(R2.id.list_of_things)
* void onItemClick(int position) {
* Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
* }
*/
Parameter[] parameters = Parameter.NONE;
//我们的方法中参数不为空,开始处理;
if (!methodParameters.isEmpty()) {
parameters = new Parameter[methodParameters.size()];
//BitSet常见的应用是那些需要对海量数据进行一些统计工作的时候,比如日志分析等
BitSet methodParameterUsed = new BitSet(methodParameters.size());
//parameterTypes为jack注解中定义的参数;methodParameters为我们的参数
String[] parameterTypes = method.parameters();
//处理我们的参数;
for (int i = 0; i < methodParameters.size(); i++) {
VariableElement methodParameter = methodParameters.get(i);
TypeMirror methodParameterType = methodParameter.asType();
if (methodParameterType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) methodParameterType;
methodParameterType = typeVariable.getUpperBound();
}
for (int j = 0; j < parameterTypes.length; j++) {
if (methodParameterUsed.get(j)) {
continue;
}
if ((isSubtypeOfType(methodParameterType, parameterTypes[j]) && isSubtypeOfType(methodParameterType, VIEW_TYPE))
|| isTypeEqual(methodParameterType, parameterTypes[j])
|| isInterface(methodParameterType)) {
parameters[i] = new Parameter(j, TypeName.get(methodParameterType));
methodParameterUsed.set(j);
break;
}
}
//参数匹配错误处理;
...//这里省略掉错误处理
}
}
以上代码主要是获取我们在用@OnClick等注解时方法的参数与定义@OnClick注解时规定参数的一个对应处理,最终保存到parameters中,已经添加注释,不过多解释,接着往下看:
//public void onClick(){};name相当于onClick;
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
//获取注解外部类对应的Builder对象;与@BindView中一样;
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
for (int id : ids) {
QualifiedId qualifiedId = elementToQualifiedId(element, id);
/**
* 是否添加到了对应的ViewBinding.Builder中,没有创建,添加;
*
* 参数:Id:通过QualifiedId生成的Id对象;
* listener:注解中的@ListenerClass注解信息
* method:生成类里边重新父类的方法;如@OnClick中的doClick();
* binding:生成的包含name/参数/判断有无@Optional 注解的 *MethodViewBinding对象;
*/
if (!builder.addMethod(getId(qualifiedId), listener, method, binding)) {
error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)",
id, enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
创建了MethodViewBinding对象,保存name、参数及是否有@Optional注解。调用了在@BindView中也用到的getOrCreateBindingBuilder(builderMap, enclosingElement);
方法获取BindingSet.Builder对象;循环每一个id创建对应的QualifiedId对象,调用builder.addMethod()方法,这里参数已经在代码中注释,看一下这个方法:
boolean addMethod(
Id id,
ListenerClass listener,
ListenerMethod method,
MethodViewBinding binding) {
ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
//ViewBinding.Builder 中的methodBindings中是否已经有了对应的方法;
if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {
return false;
}
viewBinding.addMethodBinding(listener, method, binding);
return true;
}
同样也调用了getOrCreateViewBindings(id)方法获取ViewBinding.Builder;最后将listener等信息传入viewBinding.addMethodBinding()方法。进入ViewBinding看一下:
public void addMethodBinding(ListenerClass listener, ListenerMethod method,
MethodViewBinding binding) {
//methods为一种事件方法;如:OnPageChangeListener、OnTextChangeListener中的一种
Map<ListenerMethod, Set<MethodViewBinding>> methods = methodBindings.get(listener);
//set为每一种事件下边的多个小事件;如acitivity中可能会注解
// @OnTextChanged(value = R.id.mEditV,callback =BEFORE_TEXT_CHANGED);
//@OnTextChanged(value = R.id.mEditV,callback = TEXT_CHANGED);
//@OnTextChanged(value = R.id.mEditV,callback = AFTER_TEXT_CHANGED);
// 每一次是一个;
Set<MethodViewBinding> set = null;
if (methods == null) {
methods = new LinkedHashMap<>();
methodBindings.put(listener, methods);
} else {
set = methods.get(method);
}
if (set == null) {
set = new LinkedHashSet<>();
methods.put(method, set);
}
set.add(binding);
}
里边也有缓存,有注释,就不说了,有了前边对@BindView的了解,这个就省不少事了,说白了,还是在getOrCreateBindingBuilder(builderMap, enclosingElement)这个方法中保存builderMap,最后保存erasedTargetNames.add(enclosingElement);这里有不理解的可以下载加好注释的源码多看几遍,最后会有下载地址。
到现在为止,我们关于注解解析成BindingSet.Builder对象就完了。回到我们解析事件注解的开始ButterKnifeProcessor的findAndParseTargets(RoundEnvironment env)方法,
看剩余部分,也就是将BindingSet.Builder对象生成BindingSet的过程。看代码吧:
// 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).
//builderMap===(EnclosingElement,BindingSet.Builder)
//父类的处理,生成BindingSet存入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();
//(EnclosingElement,BindingSet.Builder)
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
//erasedTargetNames里是否保存了type的父类,有直接返回,没有返回null;
TypeElement parentType = findParentType(type, erasedTargetNames);
// erasedTargetNames里没有保存type父类;
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
//erasedTargetNames保存了type父类;
BindingSet parentBinding = bindingMap.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);
}
}
}
return bindingMap;
关键代码在bindingMap.put(type, builder.build())这句,builder.Build()方法中创建了BindingSet对象,进去看看:
BindingSet build() {
ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
//viewIdMap在addMethod、addField是添加数据;每个id对应的builder;
for (ViewBinding.Builder builder : viewIdMap.values()) {
//每一个id生成ViewBinding(id, methodBindings, fieldBinding)保存到ViewBindings中;
viewBindings.add(builder.build());
}
return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
viewBindings.build(), collectionBindings.build(), resourceBindings.build(),
parentBinding);
}
}
这里重点是通过我们之前生成的所有信息new出我们的BindingSet对象返回。最终存到我们ButterKnifeProcessor中findAndParseTargets(RoundEnvironment env)方法的bindingMap对象中,再将bindingMap返回到最初的process(Set<?extends TypeElement> elements, RoundEnvironment env)方法中,进行后续处理,那就让我们看看吧:
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//注解的一系列处理,最终返回bindingMap;
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//循环生成对应类对象;
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 false;
}
可以看到后边就是循环取出bindingMap中的每一个,调用binding.brewJava(sdk)方法,那先让咱们看看这个方法:
/**
* 创建JavaFile;
* @param sdk
* @return
*/
JavaFile brewJava(int sdk) {
//包名,及要生成的文件内容;
return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
//类上边的注释;
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
最终调用了JavaFile的builder()方法,JavaFile是Javapoet包里边的类,就不说了。看一下它的builder方法的参数,第一个看方法也知道包名,第二个是啥?看看:
/**
* 创建绑定类;
* @param sdk
* @return
*/
private TypeSpec createType(int sdk) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}
//如果有parentBinding则不继承Unbinder;
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}
//注解了成员变量或者方法添加target成员变量;对应例子中---》private SimpleActivity target;;
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
//根据不同情况添加单参数构造函数;
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
//生成最终绑定的构造函数;
result.addMethod(createBindingConstructor(sdk));
//生成unbind()方法;
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
噢,my god,都是Javapoet中的东西,没错,这里就是文件内容操作了,这里我说几个里边用到的类方法吧。详细每一行代码的意思就不说了:
TypeSpec.classBuilder-------创建类相关;
.addModifiers(PUBLIC)-------添加修饰符;
Result.superclass-----------设置父类;
result.addSuperinterface----添加接口;
result.addField-------------添加成员变量;
result.addMethod------------类添加方法或构造函数;
MethodSpec.constructorBuilder()---构造函数相关;
constructor.addParameter----构造函数添加参数;
constructor.addAnnotation---构造函数添加注解;
.addStatement---------------添加代码;
.......等等吧。
关键的生成代码是这句:
//生成最终绑定的构造函数;
result.addMethod(createBindingConstructor(sdk));
所有的绑定代码,事件处理代码生成都是在这句里边,代码就不贴了啊,篇幅有点大了,想看的可以下载我标好注释的代码,我只把sample里边activity生成的对应文件贴一下吧,大家看一下:
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import com.example.butterknife.R;
import java.lang.IllegalStateException;
import java.lang.Override;
public class SimpleActivity_ViewBinding implements Unbinder {
private SimpleActivity target;
private View view2130968578;
private View view2130968579;
@UiThread
public SimpleActivity_ViewBinding(SimpleActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {
this.target = target;
View view;
target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
view2130968578 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
view2130968579 = view;
((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
target.headerViews = Utils.listOf(
Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
Context context = source.getContext();
Resources res = context.getResources();
target.butterKnife = res.getString(R.string.app_name);
target.fieldMethod = res.getString(R.string.field_method);
target.byJakeWharton = res.getString(R.string.by_jake_wharton);
target.sayHello = res.getString(R.string.say_hello);
}
@Override
@CallSuper
public void unbind() {
SimpleActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;
view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578 = null;
((AdapterView<?>) view2130968579).setOnItemClickListener(null);
view2130968579 = null;
}
}
大家可以对着生成的Java文件去看生成Java文件过程的代码,这样便于理解,可以看到它的全部操作都是在构造函数里边做的,在构造函数里边有一个,这里咱们看一下Utils. findRequiredViewAsType()方法:
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
//绑定View;findViewById(R.id.btn);
View view = findRequiredView(source, id, who);
//强转View;如:(Button)findViewById(R.id.btn);
return castView(view, id, who, cls);
}
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);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
终于看到了我们的findViewById()方法,返回了获取的view,在调用castView(view, id, who, cls)方法:
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
进行类型转换,如将View转换成Button,就是进行(Button)findViewById(id)操作。好了,回到我们process()方法:
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());
}
最终拿到了javaFile,通过辅助类filer生成文件。到这里,我们的文件生成工作就完了。也就是说,我们编译期的所有工作就完成了,之前,所有的操作都是为了生成我们的这些Java文件,生成的Java文件会与我们所写的Activity等Java文件一同编译成.class文件,所以我们在Activity中调用ButterKnife.bind(this)方法才有作用。本篇文章写到现在也已经进行了四分之三了,还剩下的就是我们调用ButterKnife.bind(this)方法后的处理了,这部分就简单了,用buns eyes想想也知道是怎么搞的,为了让大家更好的了解,咱们还是看一看吧。
4.我们Activity中调用ButterKnife.bind(this)后... ...
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
获取了decorView根布局,调用createBinding()方法:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
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);
}
}
上边代码中调用了一个findBindingConstructorForClass方法返回了一个constructor对象,后边调用这个对象的newInstance()方法,new了一个它的实例,最终返回,那么,我们猜测这里肯定是获取了当前Acitivity对应的在编译器生成的那个添加了绑定事件等信息的类的构造函数,看一下关键代码findBindingConstructorForClass(targetClass):
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
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<?> 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;
}
看见没,看见没,
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
对应到我们生成代码里就是下面这句:
@UiThread
public SimpleActivity_ViewBinding(final SimpleActivity target, View source){... ...}
就这里,好了到这里,你应该明白整个ButterKnife工作原理了吧,我在最后总结一下:首先ButterKnife在编译器通过ButterKnifeProcessor类获取每一个ButterKnife支持的注解类型,通过他获取每一个添加该注解的element,经过一系列做操作,每一个添加了注解的类最终对应生成BindingSet.Builder对象,再生成BindingSet,最后根据BindingSet生成对应的Java类。我们在Activity里边调用bind()方法就是通过当前Activity找到对应的Java生成类并获取构造方法生成实例对象,在构造方法了进行了绑定操作。啊...终于说完了,思路很清晰,实现很麻烦。在此膜拜并感谢Jack大神为我们造出了这么牛逼的利器。好了,这篇文章就写到这里吧。不明白,就照着我添加好注释的源码看,应该很快就明白了,下边就是源码下载地址。谢谢!
源码下载地址:http://download.youkuaiyun.com/detail/liuyonglei1314/9773886