首先从使用的地方来看:
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.hello) Button hello;
@BindView(R.id.list_of_things) ListView listOfThings;
@BindView(R.id.footer) TextView footer;
@BindString(R.string.app_name) String butterKnife;
@BindString(R.string.field_method) String fieldMethod;
@BindString(R.string.by_jake_wharton) String byJakeWharton;
@BindString(R.string.say_hello) String sayHello;
上面是注解的,下面是在activity的oncreated方法使用的。注解的暂时先不管他,先看一看这个bind(this),方法做了什么!
ButterKnife.bind(this);
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
可以看到这个sourceView其实就是activity的content布局的DecorView。
再跟踪到【createBinding】
findBindingConstructorForClass
这里的这个bindingCtor就是两个参数的构造方法。
然后是
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);
这里就调用了两个参数的构造方法创建一个SimpleActivity_ViewBinding对象。
@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);
view2131099658 = 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);
view2131099666 = 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);
}
这样就完成了绑定。
这个SimpleActivity_ViewBinding类是通过butterknife-compiler自动生成的。
这些代码是怎么生成的呢?
首先我们能用的注解是定义在
对这些注解的处理主要是在compiler包下面的ButterKnifeProcessor。的process方法。
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
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, debuggable);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
这里面JavaFile是javapoet这个开源库里面的工具类,主要为了生成类,待会再看
在生成之前findAndParseTargets(env)查找和解析注解。
其中的一段代码片段。其他的注解的处理类似都是走parseXXXXX。重点看下最常用的BindView吧。
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
拿到注解传入的id参数。
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
处理完解析之后,是时候来看一看编译器自动生成java类的代码了。
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());
}
}
brewJava方法
JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
createType方法
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();
}
createBindingConstructor方法
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();
}
binding.render(sdk)对view绑定的关键在这里
CodeBlock render(boolean debuggable) {
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = $T.$L(", name, UTILS, kind.factoryName);
for (int i = 0; i < ids.size(); i++) {
if (i > 0) {
builder.add(", ");
}
builder.add("\n");
Id id = ids.get(i);
boolean requiresCast = requiresCast(type);
if (!debuggable) {
if (requiresCast) {
builder.add("($T) ", type);
}
builder.add("source.findViewById($L)", id.code);
} else if (!requiresCast && !required) {
builder.add("source.findViewById($L)", id.code);
} else {
builder.add("$T.find", UTILS);
builder.add(required ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L, \"field '$L'\"", id.code, name);
if (requiresCast) {
TypeName rawType = type;
if (rawType instanceof ParameterizedTypeName) {
rawType = ((ParameterizedTypeName) rawType).rawType;
}
builder.add(", $T.class", rawType);
}
builder.add(")");
}
}
return builder.add(")").build();
}
间接调用findviewbyid()
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}