ButterKnife源码分析
1. ButterKnife 简介
1.1 代码结构
- butterknife ;android library model 提供android使用的API
- butterknife-annotations; java-model,使用时的注解
- butterknife-compiler;java-model,编译时用到的注解的处理器
- butterknife-gradle-plugin;自定义的gradle插件,辅助生成有关代码
- butterknife-integration-test;该项目的测试用例
- butterknife-lint;该项目的lint检查
1.2 流程简述
[图片上传失败...
2. ButterKnife解析
2.1
对控件进行绑定:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
Button btn;
@BindView(R.id.tv1)
TextView tv;
@OnClick(R.id.btn1)
public void btnClick() {
Toast.makeText(this, "aa", Toast.LENGTH_SHORT).show();
}
2.2 编译,生成Java代码
2.3.1
在Java代码的编译时期,javac 会调用java注解处理器来生成辅助代码。生成的代码就在 build/generated/source/apt
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
private View view2131427416;
@UiThread
public MainActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.btn1, "field 'btn' and method 'btnClick'");
//这里的 target 其实就是我们的 Activity
//这个castView就是将得到的View转化成具体的子View
target.btn = Utils.castView(view, R.id.btn1, "field 'btn'", Button.class);
view2131427416 = view;
//为按钮设置点击事件
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.btnClick();
}
});
target.tv = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.btn = null;
target.tv = null;
view2131427416.setOnClickListener(null);
view2131427416 = null;
this.target = null;
}
方法说明:
findRequiredView:这个方法就是找到布局文件配置的View。世人唾弃的findViewById方法就是在这里自动调用的。
View view = source.findViewById(id);
if (view != null) {
return view;
}
2.3.2
ButterKnife注解处理器
用来解析注解并生成对应java代码
ButterKnife注解处理器里包含下面几个重要的方法:
- init() :初始化,得到Elements、Types、Filer等工具类
- getSupportedAnnotationTypes() :描述注解处理器需要处理的注解
- process() :扫描分析注解,生成代码。这个是ButterKnife的核心,下面着重分析。
process方法:
- 获得TypeElement -> BindingSet的映射关系
- 运用JavaPoet框架来生成形式为xxxx_ViewBinding代码
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//查找所有的注解信息,并形成BindingClass保存到 map中
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
// 生成MainActivity_ViewBinding代码
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
JavaFile javaFile = bindingClass.brewJava();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
findAndParseTargets方法
省略掉无关的代码,findAndParseTargets分成三部分:
- scanForRClasses:建立view与R的id的关系,将R.id转换成MainActivity_ViewBinding中的view。
- 解析各种注解(这里以BindView为例)。
- 根据buildMap来生成bindingMap。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// 建立view与R的id的关系
scanForRClasses(env);
// 解析查找所有包含BindView注解的element
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
// 将Map.Entry<TypeElement, BindingSet.Builder>转化为Map<TypeElement, BindingSet>
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);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.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;
}
在注解处理器中,我们扫描 java 源文件,源代码中的每一部分都是Element的一个特定类型。换句话说:Element代表程序中的元素,比如说 包,类,方法。每一个元素代表一个静态的,语言级别的结构,这些都是Element的子类
比如:
public class ClassA { // TypeElement
private int var_0; // VariableElement
public ClassA() {} // ExecuteableElement
public void setA( // ExecuteableElement
int newA // TypeElement
) {
}
}
TypeMirror:
TypeMirror表示 Java 编程语言中的类型。这些类型包括基本类型、声明类型(类和接口类型)、数组类型、类型变量和 null 类型。还可以表示通配符类型参数、executable 的签名和返回类型,以及对应于包和关键字 void 的伪类型。
parseBindView方法
parseBindView先检测是否有错误,然后将name(变量名,例如tvTitle)、type(类名,例如TextView)、required(是否有@nullable注解)封装成FieldViewBinding放到builder这个Map里。
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
// 得到包含注解所属的TypeElement,例如MainActivity
//判断修饰符,如果包含private or static //就会抛出异常。
//判断父节点如果是private 类,则抛出异常
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//1. isInaccessibleViaGeneratedCode检验enclosingElement(MainActivity)是类、不是private,检验element不是private活着static
// isBindingInWrongPackage检验enclosingElement的包名是不是系统相关的类
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element);
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
// 判断element是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;
}
//2.获取id 值
int id = element.getAnnotation(BindView.class).value();
//// 3.获取 BindingClass,有缓存机制, 没有则创建
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
// 检查是否绑定过此id
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 {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
//4. 生成FieldViewBinding 实体
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
//5.FieldViewBinding加入到 bindingClass 成员变量的集合中
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
上面代码可分为5个步骤
1. 检查用户使用的合法性
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
// 得到父节点
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//判断修饰符,如果包含private or static 就会抛出异常。
// Verify method modifiers.
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
error(element, "@%s %s must not be private or static. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//判断父节点是否是类类型的,不是的话就会抛出异常
//也就是说BindView 的使用必须在一个类里
// Verify containing type.
if (enclosingElement.getKind() != CLASS) {
error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//判断父节点如果是private 类,则抛出异常
// Verify containing class visibility is not private.
if (enclosingElement.getModifiers().contains(PRIVATE)) {
error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
return hasError;
}
//不能再以android开头的包中使用
if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
//不能再以java开头的包中使用
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
2. 获取自己传入的id值
3. 获取BindingClass,如果缓存中没有就新建一个实例。
4. 获取注解名字和控件类型,生成FieldViewBinding。
5. 添加到bindingClass集合中
2.3.3 运用JavaPoet框架来生成代码
在生成Map<TypeElement,BindingSet>后,下一步就是遍历Map,调用每一个类的brewJava()方法,根据TypeElement(MainActivity)利用Filer工具类来生成文件TypeElement_ViewBinding(MainActivity_ViewBinding)。
private TypeSpec createBindingClass() {
1. 生成类名
//bindingClassName.simpleName() 就是我们在findAndParseTargets方法形成的类的名字:xxx_ViewBinding名字
xxx也就是使用butterknife的类名
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
//增加类修饰符public
.addModifiers(PUBLIC);
//使用butterknife的类是 final类
TypeName targetType;
if (isFinal) {
//增加类修饰符FINAL
result.addModifiers(FINAL);
targetType = targetTypeName;
} else {
//不是final类,增加泛型参数T
targetType = TypeVariableName.get("T");
result.addTypeVariable(TypeVariableName.get("T", targetTypeName));
}
//如果有父类直接继承
if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(getParentBinding(), targetType));
} else {
//增加实现接口
result.addSuperinterface(UNBINDER);
//增加成员变量
result.addField(targetType, "target", isFinal ? PRIVATE : PROTECTED);
}
}
2. 生成构造函数
if (!bindNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor(targetType));
}
result.addMethod(createBindingConstructor(targetType));
3. 生成unbind方法。
if (hasViewBindings() || !hasParentBinding()) {
result.addMethod(createBindingUnbindMethod(result, targetType));
}
return result.build();
}
3. ButterKnife.bind(this)
编译生成代码后,在bind方法中调用。
ButterKnife.bind的一系列方法都会调用createBinding方法
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);
}
...
这里通过获取Class获取XXX_ViewBinding的Constructor构造方法,然后调用constructor.newInstance(target, source)创建生成代码的对象。
findBindingConstructorForClass方法
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//缓存中查找,找到直接返回
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
//检查合法性
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//构造一个class,可以看类名就是我们生成的。
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
//noinspection unchecked
// 获取我们的构造函数
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);
}
//加入到缓存中
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
这里的BINGDINGS是一个Constructor的Map缓存。
4. ButterKnife学习曲线
[图片上传失败...(image-4f410b-1534244273443)]