1. 前言
他来了,他来了,他带着黄油刀走来了。
谁说不想学习的?(狗头)
2. 介绍
来自官网的介绍:
Field and method binding for Android views
也就是说 这的库是 关于Android视图中方法和变量的绑定。
不过在Github中也出现了一句提示:
Development on this tool is winding down. Please consider switching to view binding in the coming months
大概意思就是 使用这个工具开发的人在变少,建议使用数据绑定。
不过我们还是要来学习一个他的源码。
3. 正文
3.1 bind方法
简单使用案例:
class ExampleActivity extends Activity {
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}}
让我们直接进入到bind方法中去:
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
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);
}
}
我们可以看到会通过 findBindingConstructorForClass() 方法去拿到构造器constructor,constructor会通过反射的方式拿到实例,现在让我们来具体的看看 findBindingConstructorForClass() 这个方法:
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
String clsName = cls.getName();
// 如果是 java文件、androidx、android的文件就不进行处理
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
return null;
}
try {
// 在这里会加载一个 clsName_ViewBinding类的class对象
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
// 在这里获取到clsName_ViewBinding类的构造器对象
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} 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);
}
// 存入在缓存中
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
可以看到我们会先从BINDINGS中获取Constructor,如果存在的话,就直接返回。
那么BINDINGS是什么呢?
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
其实也就是维持了一个LinkedHashMap的缓存,因为反射相对的来说还是会比较消耗性能,所以维持一个缓存来降低反射的调用。
可以看到我们最后会去找一个 clsName_ViewBinding类的实例,那么这个是什么呢?
我们可以反编译打开一个apk看看:
会发现在它的目录下会生成一个 clsName_ViewBinding的Java类:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f070023;
private View view7f070025;
public MainActivity_ViewBinding(MainActivity mainActivity) {
this(mainActivity, mainActivity.getWindow().getDecorView());
}
public MainActivity_ViewBinding(final MainActivity mainActivity, View view) {
this.target = mainActivity;
View findRequiredView = Utils.findRequiredView(view, R.id.button_click, "field 'buttonClick' and method 'onClick'");
mainActivity.buttonClick = (Button) Utils.castView(findRequiredView, R.id.button_click, "field 'buttonClick'", Button.class);
this.view7f070025 = findRequiredView;
findRequiredView.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View view) {
mainActivity.onClick(view);
}
});
View findRequiredView2 = Utils.findRequiredView(view, R.id.button_2, "method 'onClick'");
this.view7f070023 = findRequiredView2;
findRequiredView2.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View view) {
mainActivity.onClick(view);
}
});
}
public void unbind() {
MainActivity mainActivity = this.target;
if (mainActivity != null) {
this.target = null;
mainActivity.buttonClick = null;
this.view7f070025.setOnClickListener((View.OnClickListener) null);
this.view7f070025 = null;
this.view7f070023.setOnClickListener((View.OnClickListener) null);
this.view7f070023 = null;
return;
}
throw new IllegalStateException("Bindings already cleared.");
}
}
我们可以看到在ButterKnife.bind()中返回的构造对象应该就是这个 MainActivity_ViewBinding的对象。
我们进入到Utils#findRequiredView()的方法中:
public static View findRequiredView(View view, int i, String str) {
View findViewById = view.findViewById(i);
if (findViewById != null) {
return findViewById;
}
String resourceEntryName = getResourceEntryName(view, i);
throw new IllegalStateException("Required view '" + resourceEntryName + "' with ID " + i + " for " + str + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.");
}
可以看到内部也是使用findViewById来找寻对象的。
那么这个类是如何生成的呢?
3.2 ButterKnifeProcessor注解处理器
其实是ButterKnife 会通过自定义的注解处理器 ButterKnifeProcessor来对扫描到的注解进行处理,
然后通过javapoet来动态的生成代码,最后运行的时候,就可以通过bind方法来完成绑定了。
我们先来看看ButterKnifeProcessor的init()方法:
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//.....
// 处理TypeMirror的工具类,用于取类信息
typeUtils = env.getTypeUtils();
// filer 可以用来创建文件
filer = env.getFiler();
//....
}
继续来看重写的方法getSupportedAnnotationTypes():
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
//-------------------
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindAnim.class);
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindFont.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
这些就是处理器能够处理的注解。
现在让我们进入到主要方法process方法中:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 这里是拿到所有的注解信息
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
// 通过循环的方式用javapoet来生成Java代码
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;
}
让我们再次进入到 findAndParseTargets() 中:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
//...... 这里是一系列的对BindXXX的处理
// Process each @BindView element.
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);
}
}
// ......
}
对注解一系列的处理后都放入到 builderMap.
我们可以来看看parseBindView() 的处理:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 这里判断的是被注解的是否是 private 和 static
// 判断是否被注解在错误的包上
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.
int id = element.getAnnotation(BindView.class).value();
// 通过类元素判断build是否存在
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
if (builder != null) {
// 判断是否已经被绑定过了
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 {
// 创建一个新的build
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
让我们再回到process方法中,通过binding.brewJava去生成一个Java类:
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();
}
进入到createType() 中:
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC)
.addOriginatingElement(enclosingElement);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.getBindingClassName());
} 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) {
// 这里就是增加unbind方法
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
这里的一系列操作其实是为了javapoet生成java源文件做准备的。
然后在 javaFile.writeTo(filer) 中去生成了源文件。
3.2.1 总结
1. 可以看到butterKnife会在编译的时候扫描注解,然后通过自定义的ButterKnifeProcessor对注解的对象进行处理,最后通过javapoet生成java代码。
2. 然后调用ButterKnife的bind()方法,会找到生成的代码完成绑定,在其中完成一系列的操作。
3.3 Library中的R2文件
我们来看看butterknife源码中的sample中的library中的案例:
可以看到其中绑定的时候 R文件变成了R2,这是为什么呢?
我们先来看看主项目中的R文件和module中的R文件的区别:
- 主项目中创建一个资源文件,会在
R.java中添加一个静态常量。- 而在子项目中创建资源文件后,
R.java中自动添加的是静态变量而不是静态常量,这就导致需要需要用的常量的地方无法使用R.xxx的方式。
那么在butterknife中注解是需要用到R文件的静态变量怎么办呢?
butterknife是这样做的,创建一个R2文件,这个R2是R文件的复制,只是在所有的值上都添加了final,让所有的变量都变成了常量。然后在生成ViewBinding类的时候再替换回来。
而这个R2文件是作者通过gradle插件来完成的,也就是butterknife-gradle-plugin。
在plugin中的 ButterKnifePlugin:
我们进入到 R2Generator中:
当task执行的时候,在这里javapoet会去生成代码。
关于R2的信息可以看看这个文章:
AOP 最后一块拼图 | AST 抽象语法树 —— 最轻量级的AOP方法
参考:
butterknife源码