ButterKnife官网
用法示例package com.example.butterknife.library;
public class SimpleActivity extends Activity {
@BindView(R.id.hello) Button hello;
@BindView(R.id.titleTv) TextView titleTv;
@OnClick(R.id.hello)
void sayHello() {
Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
hello.setText("你好");
}
}
ButterKnife.bind(this)源码
绑定activity实例,通过实例关联apt生成的对应的activity_ViewBinding类,通过顶层View关联包含的控件
@NonNull
@UiThread
public static Unbinder bind(@NonNull Activity target) {
//获取当前页面的顶层视图
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
@NonNull
@UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
//targetClass如com.example.butterknife.library.SimpleActivity
//查找包+类名+_ViewBinding的构造函数
Constructor extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
//通过构造函数如public com.example.butterknife.library.SimpleActivity_ViewBinding(com.example.butterknife.library.SimpleActivity, android.view.View)
//创建SimpleActivity_ViewBinding实例
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);
}
}
找出_ViewBinding的构造方法
@Nullable
@CheckResult
@UiThread
private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) {
//缓存中获取_ViewBinding类的构造函数
Constructor extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
//获取包类路径[如com.example.butterknife.library.SimpleActivity]
String clsName = cls.getName();
//过滤
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//加载在编译期生成的_ViewBinding类[如SimpleActivity_ViewBinding]
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
//获取_ViewBinding类的构造函数[如public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {}]
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);
}
//_ViewBinding类和对应构造函数置入缓存
//如页面A反复创建销毁多次后再创建进入bind(this)方法可以从缓存中获取对应的A_ViewBinding类
//如(com.example.butterknife.library.SimpleActivity, public com.example.butterknife.library.SimpleActivity_ViewBinding(com.example.butterknife.library.SimpleActivity, android.view.View))
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
apt生成的_ViewBinding代码示例
apt处理后生成的和activity关联的_ViewBinding类,管理了activity内的各个控件,变量和控件id仍是通过findViewById关联
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;
public class SimpleActivity_ViewBinding implements Unbinder {
private SimpleActivity target;
private View view7f05000a;
@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;
//findRequiredView就是用findViewById关联变量和控件id
view = Utils.findRequiredView(source, R.id.hello, "field 'hello' and method 'sayHello'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
view7f05000a = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
target.titleTv = Utils.findRequiredViewAsType(source, R.id.titleTv, "field 'titleTv'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
SimpleActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.hello = null;
target.titleTv = null;
view7f05000a.setOnClickListener(null);
view7f05000a = null;
}
}
apt核心流程源码
TypeElement如SimpleActivity,BindingSet如hello/titleTv等控件信息,根据bindingMap写_ViewBinding文件,该文件包含页面和页面内的控件集合信息,参考上面生成的文件
@Override
public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
//map存的是类和类中被ButterKnife注解的元素及相关信息
//如SimpleActivity有@BindView(R.id.hello) Button hello; @BindView(R.id.titleTv) TextView titleTv;等
//XXActivity有@BindView(R.id.tv) TextView tv; @BindView(R.id.bt) Button bt;等
Map bindingMap = findAndParseTargets(env);
for (Map.Entry entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//将生成的类和类对应的控件集合等信息,添加必要的构造类的语句,写入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());
}
}
return false;
}
private MapfindAndParseTargets(RoundEnvironment env) { //BindingSet为将要写入java文件的控件集合信息 Map builderMap = new linkedHashMap<>(); Set erasedTargetNames = new linkedHashSet<>(); //...@BindAnim/@BindArray等处理,参考下面@BindView逻辑 // Process each @BindView element. //处理被@BindView注解的元素,处理拼接记录信息以生成代码写入Filer 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); } } //... //参考process方法内的注释 //就是从builderMap,erasedTargetNames中获取将要生成代码的信息,在处理一下存储至bindingMap返回 Map classpathBindings = findAllSupertypeBindings(builderMap, erasedTargetNames); // 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). Deque > entries = new ArrayDeque<>(builderMap.entrySet()); Map bindingMap = new linkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry 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); } } } return bindingMap; }
private void parseBindView(Element element, Map概括builderMap, Set erasedTargetNames) { //enclosingElement为element的父元素[如MainActivity] TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Start by verifying common generated code restrictions. //检查元素 //isInaccessibleViaGeneratedCode=>element不是private或static修饰,且enclosingElement是class且不被private修饰 //isBindingInWrongPackage=>enclosingElement类不在android.或java.系统包下 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(); //检查元素类型不是View&&不是接口 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. //收集element信息[id如xml中的R.id.tv] int id = element.getAnnotation(BindView.class).value(); //收集enclosingElement内的所有将要绑定的控件id信息[存于BindingSet中] BindingSet.Builder builder = builderMap.get(enclosingElement); Id resourceId = elementToId(element, BindView.class, id); if (builder != null) { //检查builderMap已包含该控件id 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 { //如存储SimpleActivity中控件信息的BindingSet为空则初始化 builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); //检查元素是否被@Nullable注解声明 boolean required = isFieldRequired(element); //将当前控件信息和控件id存入BindingSet中 builder.addField(resourceId, new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. //记录父元素 erasedTargetNames.add(enclosingElement); }
apt根据被@BindView注解的元素,获取相关信息生成java文件;如根据@BindView(R.id.hello)生成了[SimpleActivity, hello控件id及相关信息],此时又处理@BindView(R.id.titleTv),通过process方法/parseBindView方法生成了[SimpleActivity, hello控件id及相关信息, titleTv控件id及相关系],将这些信息存进bindingMap中,bindingMap的BindingSet中存储了控件的相关信息[可以自行查看BindingSet源码,会清楚很多],完了就把这些activity和对应的控件信息写到_ViewBinding文件中;
activity中bind(this)后,通过this实例找到_ViewBinding构造方法,加载activity_ViewBinding类,在activity_ViewBinding中控件仍是通过findViewById来关联的



