@Retention(RUNTIME)@Target(ANNOTATION_TYPE) public@interface ListenerClass { String targetType();//目标类型 /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */ String setter();//将该类设置给目标类型的方法 /** * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If * empty {@link #setter()} will be used by default. */ String remover()default ""; /** Fully-qualified class name of the listener type. */ String type();
/** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */ Class<? extends Enum<?>> callbacks() default NONE.class;
/** * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()} * and an error to specify more than one value. */ ListenerMethod[] method() default { };
/** Default value for {@link #callbacks()}. */ enumNONE{ } }
@ListenerMethod注解代表一个监听类的方法,因为类里面可能有多个方法,所以是一个数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@Retention(RUNTIME)@Target(FIELD) public@interface ListenerMethod { /** Name of the listener method for which this annotation applies. */ String name();
/** List of method parameters. If the type is not a primitive it must be fully-qualified. */ String[] parameters() default { };
/** Primitive or fully-qualified return type of the listener method. May also be {@code void}. */ String returnType()default "void";
/** If {@link #returnType()} is not {@code void} this value is returned when no binding exists. */ String defaultReturn()default "null"; }
@OnClick
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@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. */ @IdResint[] value() default { View.NO_ID }; }
TypeMirror elementType = element.asType(); TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); //例如当@BindView T view; 这时候就是一个TypeKind.TYPEVAR类型 if (elementType.getKind() == TypeKind.TYPEVAR) {//泛型类型 TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound();//获取上边界 } if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {//判断field的类型 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; } int id = element.getAnnotation(BindView.class).value();//获取id BindingSet.Builder builder = builderMap.get(enclosingElement);//创建 BindingSet.Builder if (builder != null) { ViewBindings viewBindings = builder.getViewBinding(getId(id)); //当FieldBinding已经存在说明已经绑定过了。重复绑定将抛异常 if (viewBindings != null && viewBindings.getFieldBinding() != null) { FieldViewBinding existingBinding = viewBindings.getFieldBinding();
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBinding.getName(), enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } else { builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = element.getSimpleName().toString();//获取FieldName TypeName type = TypeName.get(elementType);//获取Field的Type boolean required = isFieldRequired(element);//是否必须 builder.addField(getId(id), new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); }
privatevoidparseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) throws Exception { // This should be guarded by the annotation's @Target but it's worth a check for safe casting. if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) { thrownew IllegalStateException( String.format("@%s annotation must be on a method.", annotationClass.getSimpleName())); }
ExecutableElement executableElement = (ExecutableElement) element; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Assemble information on the method. Annotation annotation = element.getAnnotation(annotationClass); Method annotationValue = annotationClass.getDeclaredMethod("value"); if (annotationValue.getReturnType() != int[].class) { thrownew IllegalStateException( String.format("@%s annotation value() type not int[].", annotationClass)); } int[] ids = (int[]) annotationValue.invoke(annotation);//通过反射获取值 String name = executableElement.getSimpleName().toString();//方法名 boolean required = isListenerRequired(executableElement); // Verify that the method and its containing class are accessible via generated code. boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element); hasError |= isBindingInWrongPackage(annotationClass, element); //发现重复的id Integer duplicateId = findDuplicate(ids); if (duplicateId != null) { error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)", annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; }
ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class); if (listener == null) { thrownew IllegalStateException( String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(), annotationClass.getSimpleName())); } for (int id : ids) { if (id == NO_ID.value) { if (ids.length == 1) { if (!required) { error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)", enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } } else { error(element, "@%s annotation contains invalid ID %d. (%s.%s)", annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } } }
ListenerMethod method; ListenerMethod[] methods = listener.method(); if (methods.length > 1) { thrownew IllegalStateException(String.format("Multiple listener methods specified on @%s.", annotationClass.getSimpleName())); } elseif (methods.length == 1) { if (listener.callbacks() != ListenerClass.NONE.class) { thrownew IllegalStateException( String.format("Both method() and callback() defined on @%s.", annotationClass.getSimpleName())); } method = methods[0]; } else { 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) { thrownew IllegalStateException( String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(), annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(), callback.name())); } }
// Verify that the method has equal to or less than the number of parameters as the listener. List<? extends VariableElement> methodParameters = executableElement.getParameters(); if (methodParameters.size() > method.parameters().length) { error(element, "@%s methods can have at most %s parameter(s). (%s.%s)", annotationClass.getSimpleName(), method.parameters().length, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify method return type matches the listener. TypeMirror returnType = executableElement.getReturnType(); if (returnType instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) returnType; returnType = typeVariable.getUpperBound(); } if (!returnType.toString().equals(method.returnType())) { error(element, "@%s methods must have a '%s' return type. (%s.%s)", annotationClass.getSimpleName(), method.returnType(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } if (hasError) { return; } Parameter[] parameters = Parameter.NONE; if (!methodParameters.isEmpty()) { parameters = new Parameter[methodParameters.size()]; BitSet methodParameterUsed = new BitSet(methodParameters.size()); 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]) || isInterface(methodParameterType)) { parameters[i] = new Parameter(j, TypeName.get(methodParameterType)); methodParameterUsed.set(j); break; } } if (parameters[i] == null) { StringBuilder builder = new StringBuilder(); builder.append("Unable to match @") .append(annotationClass.getSimpleName()) .append(" method arguments. (") .append(enclosingElement.getQualifiedName()) .append('.') .append(element.getSimpleName()) .append(')'); for (int j = 0; j < parameters.length; j++) { Parameter parameter = parameters[j]; builder.append("\n\n Parameter #") .append(j + 1) .append(": ") .append(methodParameters.get(j).asType().toString()) .append("\n "); if (parameter == null) { builder.append("did not match any listener parameters"); } else { builder.append("matched listener parameter #") .append(parameter.getListenerPosition() + 1) .append(": ") .append(parameter.getType()); } } builder.append("\n\nMethods may have up to ") .append(method.parameters().length) .append(" parameter(s):\n"); for (String parameterType : method.parameters()) { builder.append("\n ").append(parameterType); } builder.append( "\n\nThese may be listed in any order but will be searched for from top to bottom."); error(executableElement, builder.toString()); return; } } }
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); for (int id : ids) { if (!builder.addMethod(getId(id), 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); }
// Verify that the target type is String. if (!STRING_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'String'. (%s.%s)", BindString.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; }
if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE);//添加字段target } // if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor(targetTypeName)); } result.addMethod(createBindingConstructor(targetTypeName, sdk));
if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result, targetTypeName)); }
if (hasUnqualifiedResourceBindings()) {//添加SuppressWarnings注解 // 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()); } if (parentBinding != null) {//添加父类构造方法的调用 if (parentBinding.constructorNeedsView()) { constructor.addStatement("super(target, source)"); } elseif (constructorNeedsView()) { constructor.addStatement("super(target, source.getContext())"); } else { constructor.addStatement("super(target, context)"); } constructor.addCode("\n"); } if (hasTargetField()) {// constructor.addStatement("this.target = target"); constructor.addCode("\n"); }
if (hasViewBindings()) { if (hasViewLocal()) { // Local variable in which all views will be temporarily stored. constructor.addStatement("$T view", VIEW); } for (ViewBindings bindings : viewBindings) { addViewBindings(constructor, bindings); } for (FieldCollectionViewBinding binding : collectionBindings) { constructor.addStatement("$L", binding.render()); } if (!resourceBindings.isEmpty()) { constructor.addCode("\n"); } } // 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(); }
// We only need to emit the null check if there are zero required bindings. boolean needsNullChecked = bindings.getRequiredBindings().isEmpty(); if (needsNullChecked) { result.beginControlFlow("if (view != null)"); }
// Add the view reference to the binding. String fieldName = "viewSource"; String bindName = "source"; if (!bindings.isBoundToRoot()) { fieldName = "view" + bindings.getId().value; bindName = "view"; } result.addStatement("$L = $N", fieldName, bindName);
for (Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> e : classMethodBindings.entrySet()) { ListenerClass listener = e.getKey(); Map<ListenerMethod, Set<MethodViewBinding>> methodBindings = e.getValue();