用法示例
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<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> 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 Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
//BindingSet为将要写入java文件的控件集合信息
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> 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<TypeElement, ClasspathBindingSet> 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<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();
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<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> 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来关联的