1.annotationProcessor 注解处理器,就是在编译阶段处理注解用的。可以通过它在编译的时候自动生成一些需要的代码,好像都是这么用的。
ButterKnife
1.bind 就是找到Unbinder 实例的构造方法,然后创建一个Unbinder 实例
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
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);
}
}
2.Unbinder 实例如何工作的?
public class MainActivity_ViewBinding implements Unbinder
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
target.mSlMain = Utils.findRequiredViewAsType(source, R.id.sl_main, "field 'mSlMain'", VerticalDrawerLayout.class);
view = Utils.findRequiredView(source, R.id.ll_bottom_bar, "field 'mLlBottomBar' and method 'onViewClicked'");
target.mLlBottomBar = Utils.castView(view, R.id.ll_bottom_bar, "field 'mLlBottomBar'", LinearLayout.class);
view2131296444 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onViewClicked(p0);
}
});
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.");
}
可以看到构造方法里面直接为activity 的属性赋值(还是findviewbyid),这就可以理解为啥BindView 注解的成员变量不能是私有的了
3.Unbinder 实现类 是从哪里来的?
@AutoService(Processor.class)
@SuppressWarnings("NullAway") // TODO fix all these...
public final class ButterKnifeProcessor extends AbstractProcessor
关键就是下面的这个方法中
@Override public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
Map bindingMap = findAndParseTargets(env);
for (Map.Entry 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;
}
package butterknife.compiler;
import butterknife.OnTouch;
import butterknife.internal.ListenerClass;
import butterknife.internal.ListenerMethod;
import com.google.common.collect.ImmutableList;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import static butterknife.compiler.ButterKnifeProcessor.ACTIVITY_TYPE;
import static butterknife.compiler.ButterKnifeProcessor.DIALOG_TYPE;
import static butterknife.compiler.ButterKnifeProcessor.VIEW_TYPE;
import static butterknife.compiler.ButterKnifeProcessor.isSubtypeOfType;
import static com.google.auto.common.MoreElements.getPackage;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
/** A set of all the bindings requested by a single type. */
final class BindingSet {
static final ClassName UTILS = ClassName.get("butterknife.internal", "Utils");
private static final ClassName VIEW = ClassName.get("android.view", "View");
private static final ClassName CONTEXT = ClassName.get("android.content", "Context");
private static final ClassName RESOURCES = ClassName.get("android.content.res", "Resources");
private static final ClassName UI_THREAD =
ClassName.get("androidx.annotation", "UiThread");
private static final ClassName CALL_SUPER =
ClassName.get("androidx.annotation", "CallSuper");
private static final ClassName SUPPRESS_LINT =
ClassName.get("android.annotation", "SuppressLint");
private static final ClassName UNBINDER = ClassName.get("butterknife", "Unbinder");
static final ClassName BITMAP_FACTORY = ClassName.get("android.graphics", "BitmapFactory");
static final ClassName CONTEXT_COMPAT =
ClassName.get("androidx.core.content", "ContextCompat");
static final ClassName ANIMATION_UTILS =
ClassName.get("android.view.animation", "AnimationUtils");
private final TypeName targetTypeName;
private final ClassName bindingClassName;
private final boolean isFinal;
private final boolean isView;
private final boolean isActivity;
private final boolean isDialog;
private final ImmutableList viewBindings;
private final ImmutableList collectionBindings;
private final ImmutableList resourceBindings;
private final @Nullable BindingSet parentBinding;
private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog, ImmutableList viewBindings,
ImmutableList collectionBindings,
ImmutableList resourceBindings, @Nullable BindingSet parentBinding) {
this.isFinal = isFinal;
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
this.viewBindings = viewBindings;
this.collectionBindings = collectionBindings;
this.resourceBindings = resourceBindings;
this.parentBinding = parentBinding;
}
JavaFile brewJava(int sdk, boolean debuggable) {
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}
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, debuggable));
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
private MethodSpec createBindingViewDelegateConstructor() {
return MethodSpec.constructorBuilder()
.addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.\n "
+ "Only present for runtime invocation through {@code ButterKnife.bind()}.\n",
bindingClassName, targetTypeName, CONTEXT)
.addAnnotation(Deprecated.class)
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target")
.addParameter(VIEW, "source")
.addStatement(("this(target, source.getContext())"))
.build();
}
private MethodSpec createBindingConstructorForView() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
builder.addStatement("this(target, target)");
} else {
builder.addStatement("this(target, target.getContext())");
}
return builder.build();
}
private MethodSpec createBindingConstructorForActivity() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
builder.addStatement("this(target, target.getWindow().getDecorView())");
} else {
builder.addStatement("this(target, target)");
}
return builder.build();
}
private MethodSpec createBindingConstructorForDialog() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
builder.addStatement("this(target, target.getWindow().getDecorView())");
} else {
builder.addStatement("this(target, target.getContext())");
}
return builder.build();
}
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
constructor.addParameter(targetTypeName, "target");
}
if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");
} else {
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());
}
if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}
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");
}
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 (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding, debuggable);
}
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render(debuggable));
}
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();
}
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(PUBLIC);
if (!isFinal && parentBinding == null) {
result.addAnnotation(CALL_SUPER);
}
if (hasTargetField()) {
if (hasFieldBindings()) {
result.addStatement("$T target = this.target", targetTypeName);
}
result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
"Bindings already cleared.");
result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
result.addCode("\n");
for (ViewBinding binding : viewBindings) {
if (binding.getFieldBinding() != null) {
result.addStatement("target.$L = null", binding.getFieldBinding().getName());
}
}
for (FieldCollectionViewBinding binding : collectionBindings) {
result.addStatement("target.$L = null", binding.name);
}
}
if (hasMethodBindings()) {
result.addCode("\n");
for (ViewBinding binding : viewBindings) {
addFieldAndUnbindStatement(bindingClass, result, binding);
}
}
if (parentBinding != null) {
result.addCode("\n");
result.addStatement("super.unbind()");
}
return result.build();
}
private void addFieldAndUnbindStatement(TypeSpec.Builder result, MethodSpec.Builder unbindMethod,
ViewBinding bindings) {
// Only add fields to the binding if there are method bindings.
Map>> classMethodBindings =
bindings.getMethodBindings();
if (classMethodBindings.isEmpty()) {
return;
}
String fieldName =
bindings.isBoundToRoot()
? "viewSource"
: "view" + Integer.toHexString(bindings.getId().value);
result.addField(VIEW, fieldName, PRIVATE);
// We only need to emit the null check if there are zero required bindings.
boolean needsNullChecked = bindings.getRequiredBindings().isEmpty();
if (needsNullChecked) {
unbindMethod.beginControlFlow("if ($N != null)", fieldName);
}
for (ListenerClass listenerClass : classMethodBindings.keySet()) {
// We need to keep a reference to the listener
// in case we need to unbind it via a remove method.
boolean requiresRemoval = !"".equals(listenerClass.remover());
String listenerField = "null";
if (requiresRemoval) {
TypeName listenerClassName = bestGuess(listenerClass.type());
listenerField = fieldName + ((ClassName) listenerClassName).simpleName();
result.addField(listenerClassName, listenerField, PRIVATE);
}
String targetType = listenerClass.targetType();
if (!VIEW_TYPE.equals(targetType)) {
unbindMethod.addStatement("(($T) $N).$N($N)", bestGuess(targetType),
fieldName, removerOrSetter(listenerClass, requiresRemoval), listenerField);
} else {
unbindMethod.addStatement("$N.$N($N)", fieldName,
removerOrSetter(listenerClass, requiresRemoval), listenerField);
}
if (requiresRemoval) {
unbindMethod.addStatement("$N = null", listenerField);
}
}
unbindMethod.addStatement("$N = null", fieldName);
if (needsNullChecked) {
unbindMethod.endControlFlow();
}
}
private String removerOrSetter(ListenerClass listenerClass, boolean requiresRemoval) {
return requiresRemoval
? listenerClass.remover()
: listenerClass.setter();
}
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());
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
builder.add("($T) ", fieldBinding.getType());
}
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 requiredBindings = binding.getRequiredBindings();
if (!debuggable || 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, debuggable);
addMethodBindings(result, binding, debuggable);
}
private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
if (fieldBinding != null) {
if (requiresCast(fieldBinding.getType())) {
if (debuggable) {
result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)",
fieldBinding.getName(), UTILS, binding.getId().code,
asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType());
} else {
result.addStatement("target.$L = ($T) view", fieldBinding.getName(),
fieldBinding.getType());
}
} else {
result.addStatement("target.$L = view", fieldBinding.getName());
}
}
}
private void addMethodBindings(MethodSpec.Builder result, ViewBinding binding,
boolean debuggable) {
Map>> classMethodBindings =
binding.getMethodBindings();
if (classMethodBindings.isEmpty()) {
return;
}
// We only need to emit the null check if there are zero required bindings.
boolean needsNullChecked = binding.getRequiredBindings().isEmpty();
if (needsNullChecked) {
result.beginControlFlow("if (view != null)");
}
// Add the view reference to the binding.
String fieldName = "viewSource";
String bindName = "source";
if (!binding.isBoundToRoot()) {
fieldName = "view" + Integer.toHexString(binding.getId().value);
bindName = "view";
}
result.addStatement("$L = $N", fieldName, bindName);
for (Map.Entry>> e
: classMethodBindings.entrySet()) {
ListenerClass listener = e.getKey();
Map> methodBindings = e.getValue();
TypeSpec.Builder callback = TypeSpec.anonymousClassBuilder("")
.superclass(ClassName.bestGuess(listener.type()));
for (ListenerMethod method : getListenerMethods(listener)) {
MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name())
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(bestGuess(method.returnType()));
String[] parameterTypes = method.parameters();
for (int i = 0, count = parameterTypes.length; i < count; i++) {
callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i);
}
boolean hasReturnValue = false;
CodeBlock.Builder builder = CodeBlock.builder();
Set methodViewBindings = methodBindings.get(method);
if (methodViewBindings != null) {
for (MethodViewBinding methodBinding : methodViewBindings) {
if (methodBinding.hasReturnValue()) {
hasReturnValue = true;
builder.add("return "); // TODO what about multiple methods?
}
builder.add("target.$L(", methodBinding.getName());
List parameters = methodBinding.getParameters();
String[] listenerParameters = method.parameters();
for (int i = 0, count = parameters.size(); i < count; i++) {
if (i > 0) {
builder.add(", ");
}
Parameter parameter = parameters.get(i);
int listenerPosition = parameter.getListenerPosition();
if (parameter.requiresCast(listenerParameters[listenerPosition])) {
if (debuggable) {
builder.add("$T.castParam(p$L, $S, $L, $S, $L, $T.class)", UTILS,
listenerPosition, method.name(), listenerPosition, methodBinding.getName(), i,
parameter.getType());
} else {
builder.add("($T) p$L", parameter.getType(), listenerPosition);
}
} else {
builder.add("p$L", listenerPosition);
}
}
builder.add(");\n");
}
}
if (!"void".equals(method.returnType()) && !hasReturnValue) {
builder.add("return $L;\n", method.defaultReturn());
}
callbackMethod.addCode(builder.build());
callback.addMethod(callbackMethod.build());
}
boolean requiresRemoval = listener.remover().length() != 0;
String listenerField = null;
if (requiresRemoval) {
TypeName listenerClassName = bestGuess(listener.type());
listenerField = fieldName + ((ClassName) listenerClassName).simpleName();
result.addStatement("$L = $L", listenerField, callback.build());
}
String targetType = listener.targetType();
if (!VIEW_TYPE.equals(targetType)) {
result.addStatement("(($T) $N).$L($L)", bestGuess(targetType), bindName,
listener.setter(), requiresRemoval ? listenerField : callback.build());
} else {
result.addStatement("$N.$L($L)", bindName, listener.setter(),
requiresRemoval ? listenerField : callback.build());
}
}
if (needsNullChecked) {
result.endControlFlow();
}
}
private static List getListenerMethods(ListenerClass listener) {
if (listener.method().length == 1) {
return Arrays.asList(listener.method());
}
try {
List methods = new ArrayList<>();
Class extends Enum>> callbacks = listener.callbacks();
for (Enum> callbackMethod : callbacks.getEnumConstants()) {
Field callbackField = callbacks.getField(callbackMethod.name());
ListenerMethod method = callbackField.getAnnotation(ListenerMethod.class);
if (method == null) {
throw new IllegalStateException(String.format("@%s's %s.%s missing @%s annotation.",
callbacks.getEnclosingClass().getSimpleName(), callbacks.getSimpleName(),
callbackMethod.name(), ListenerMethod.class.getSimpleName()));
}
methods.add(method);
}
return methods;
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}
static String asHumanDescription(Collection extends MemberViewBinding> bindings) {
Iterator extends MemberViewBinding> iterator = bindings.iterator();
switch (bindings.size()) {
case 1:
return iterator.next().getDescription();
case 2:
return iterator.next().getDescription() + " and " + iterator.next().getDescription();
default:
StringBuilder builder = new StringBuilder();
for (int i = 0, count = bindings.size(); i < count; i++) {
if (i != 0) {
builder.append(", ");
}
if (i == count - 1) {
builder.append("and ");
}
builder.append(iterator.next().getDescription());
}
return builder.toString();
}
}
private static TypeName bestGuess(String type) {
switch (type) {
case "void": return TypeName.VOID;
case "boolean": return TypeName.BOOLEAN;
case "byte": return TypeName.BYTE;
case "char": return TypeName.CHAR;
case "double": return TypeName.DOUBLE;
case "float": return TypeName.FLOAT;
case "int": return TypeName.INT;
case "long": return TypeName.LONG;
case "short": return TypeName.SHORT;
default:
int left = type.indexOf('<');
if (left != -1) {
ClassName typeClassName = ClassName.bestGuess(type.substring(0, left));
List typeArguments = new ArrayList<>();
do {
typeArguments.add(WildcardTypeName.subtypeOf(Object.class));
left = type.indexOf('<', left + 1);
} while (left != -1);
return ParameterizedTypeName.get(typeClassName,
typeArguments.toArray(new TypeName[typeArguments.size()]));
}
return ClassName.bestGuess(type);
}
}
/** True when this type's bindings require a view hierarchy. */
private boolean hasViewBindings() {
return !viewBindings.isEmpty() || !collectionBindings.isEmpty();
}
/** True when this type's bindings use raw integer values instead of {@code R} references. */
private boolean hasUnqualifiedResourceBindings() {
for (ResourceBinding binding : resourceBindings) {
if (!binding.id().qualifed) {
return true;
}
}
return false;
}
/** True when this type's bindings use Resource directly instead of Context. */
private boolean hasResourceBindingsNeedingResource(int sdk) {
for (ResourceBinding binding : resourceBindings) {
if (binding.requiresResources(sdk)) {
return true;
}
}
return false;
}
private boolean hasMethodBindings() {
for (ViewBinding bindings : viewBindings) {
if (!bindings.getMethodBindings().isEmpty()) {
return true;
}
}
return false;
}
private boolean hasOnTouchMethodBindings() {
for (ViewBinding bindings : viewBindings) {
if (bindings.getMethodBindings()
.containsKey(OnTouch.class.getAnnotation(ListenerClass.class))) {
return true;
}
}
return false;
}
private boolean hasFieldBindings() {
for (ViewBinding bindings : viewBindings) {
if (bindings.getFieldBinding() != null) {
return true;
}
}
return !collectionBindings.isEmpty();
}
private boolean hasTargetField() {
return hasFieldBindings() || hasMethodBindings();
}
private boolean hasViewLocal() {
for (ViewBinding bindings : viewBindings) {
if (bindings.requiresLocal()) {
return true;
}
}
return false;
}
/** True if this binding requires a view. Otherwise only a context is needed. */
private boolean constructorNeedsView() {
return hasViewBindings() //
|| (parentBinding != null && parentBinding.constructorNeedsView());
}
static boolean requiresCast(TypeName type) {
return !VIEW_TYPE.equals(type.toString());
}
@Override public String toString() {
return bindingClassName.toString();
}
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);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
static final class Builder {
private final TypeName targetTypeName;
private final ClassName bindingClassName;
private final boolean isFinal;
private final boolean isView;
private final boolean isActivity;
private final boolean isDialog;
private @Nullable BindingSet parentBinding;
private final Map viewIdMap = new LinkedHashMap<>();
private final ImmutableList.Builder collectionBindings =
ImmutableList.builder();
private final ImmutableList.Builder resourceBindings = ImmutableList.builder();
private Builder(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog) {
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isFinal = isFinal;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
}
void addField(Id id, FieldViewBinding binding) {
getOrCreateViewBindings(id).setFieldBinding(binding);
}
void addFieldCollection(FieldCollectionViewBinding binding) {
collectionBindings.add(binding);
}
boolean addMethod(
Id id,
ListenerClass listener,
ListenerMethod method,
MethodViewBinding binding) {
ViewBinding.Builder viewBinding = getOrCreateViewBindings(id);
if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {
return false;
}
viewBinding.addMethodBinding(listener, method, binding);
return true;
}
void addResource(ResourceBinding binding) {
resourceBindings.add(binding);
}
void setParent(BindingSet parent) {
this.parentBinding = parent;
}
@Nullable String findExistingBindingName(Id id) {
ViewBinding.Builder builder = viewIdMap.get(id);
if (builder == null) {
return null;
}
FieldViewBinding fieldBinding = builder.fieldBinding;
if (fieldBinding == null) {
return null;
}
return fieldBinding.getName();
}
private ViewBinding.Builder getOrCreateViewBindings(Id id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}
BindingSet build() {
ImmutableList.Builder viewBindings = ImmutableList.builder();
for (ViewBinding.Builder builder : viewIdMap.values()) {
viewBindings.add(builder.build());
}
return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
viewBindings.build(), collectionBindings.build(), resourceBindings.build(),
parentBinding);
}
}
}
利用javapoet 生成对应的Unbinder class,原理很简单。
总结:
1.编译的时候遇到@BindView 或者其他在 processor 里面添加的其他注解,就会去生成对应的Unbinder 实现类 文件
2.调用ButterKinfe.bind 方法会去查询 Unbinder 的构造方法并实例化一个Unbinder,在Unbinder 的构造方法里面去初始化那些注解绑定的view id->findviewById
3.难点: javapoet 生成 实现类