深度学一下Butterknife是如何帮我们自动生成findViewById的
第一行
ButterKnife.bind(this);
点开
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
获取 DecorView
,DecorView
包含setContentView
的xml和 ActionBar
,DecorView
又被 PhoneWindow
包含。
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
//获取Class对象
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
//获取constructor对象
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
}
Class类:在java中,每个定义的类都会有一个相应的 Class
。在编译完成后生成在 .class
文件中,它是一个类,在long包中,私有构造函数,像基本类型、数组、void也都有一个 Class
是用来表示这个类的类型信息的。Java在运行时,系统会对所有对象进行 运行时类型标识,虚拟机运行时会以准确的方法执行。
Class:获取
- 获取Class.forName(“类的全限定名”)
- 实例对象.getClass()
- 类名.class (类字面常量)
使用 - Class.forName("类名")装入类,只做静态初始化,返回Class
- newInstance()调用无惨构造函数返回实例
- isInstance(..)判断里面的对象是否是子类
- isInterface(..)判断接口
- getSimpleName()获取对象类名等等,都是和当前类相关的内容
类名.class
和Class
类的区别:
类名.class
是 JVM将类装入内存 (还未装入内存),不做初始化工作,返回Class对象。只能用类名点出class,Button.class
Constructor(构造器)类:提供有关类 的构造函数,比如String.class未被装入内存,可以直接获取他的构造函数
Constructor<?>[] c = String.class.getConstructors();
之后可以通过对象 c
找到需要的构造函数进行 newInstance(..)
实例化
findBindingConstructorForClass()
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//这里做了一个缓存,如果缓存有直接返回
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;
}
//获取类名
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 返回Class对象
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//根据Class对象 返回 构造函数类 对象
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;
}
根据类名_ViewBinding返回Class对象,比如你的一个界面叫 MainActivity ,注解生成器会生成一个类文件叫 MainActivity_ViewBinding。
ButterKnife类主要功能就是,运行注解处理器 生成出来的文件。
关键还是在于ButterKnife的注解处理类
ButterKnifeProcessor extends AbstractProcessor
Java注解处理器,简单来说就是在编译时,扫描所有文件,收集带有注解的变量、类方法等等统一到一块,主要是 编译时
public final class ButterKnifeProcessor extends AbstractProcessor {
//变量名
//定义了一些字符串 用private static final String修饰
//定义了List放的是一些 事件 的 注解集合
@Override public synchronized void init(ProcessingEnvironment env) {
//初始化一些内容
}
//支持的注解类型 意思就是 添加了 才能用
@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;
}
//生成代码的部分
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//寻找解析注解
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//遍历上面的map 循环的生成文件
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//生成JavaFile对象
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;
}
}
bindingMap:寻找注解,以Key-value的方式存储
key:TypeElement类型元素,大概包含包名、类名、文件路径等等,我理解为“类信息”
value:BindingSet是一个所有绑定的集合,也就是 “类”
里面的有 需要绑定的集合
下面是里面代码
1、findAndParseTargets(env)
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
//这个不是最后返回的对象,这个只存了BindAnim、BindString、BindView等等的数据
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
//隐藏代码@BindAnim
//影藏很多 bind
//隐藏代码@BindString
//绑定 界面上的View
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);
}
}
//隐藏代码@BindViews
//隐藏代码 bindlistener.
Map<TypeElement, ClasspathBindingSet> classpathBindings =
findAllSupertypeBindings(builderMap, erasedTargetNames);
//使用队列的方式,绑定superClass
// 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;
}
这里分2块内容
第一部分是查找,BindString、BindView等等 的类
第二部分是把所有的类进行联合变成 树
第一部分
//关键是这里BindView, 先不看BindString,BindAnim等等 都是相通的
parseBindView(element, builderMap, erasedTargetNames);
//直接贴代码
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//校验类型
//方法名意思是:生成后的代码 无法访问一些内容,比如private等等
//isBindingInWrongPackage:绑定的内容 在错误的包
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// 验证类型是否从View扩展,就是是否是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;
}
//开始收集所有信息
//获取id
int id = element.getAnnotation(BindView.class).value();
//获取一个builder,这个builder是从builderMap中取的,这个builderMap是传进来的
//参数enclosingElement 是这样赋值的 (TypeElement) element.getEnclosingElement();
//这个TypeElement 我之前写了是 理解为 “类信息” 的
//所以是用get方法取, 因为第一个绑定方法是BindAnim,有可能这里已经 创建过了
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
//判断builder是否为空
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 {
//为空 则生成一个Builder出来
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
//获取名字、类型
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
//是否有 Nullable 标签
boolean required = isFieldRequired(element);
//在builder中添加一个Field ,主要是FieldViewBinding,如果是@BindDrawable则会用FieldDrawableBinding类
builder.addField(resourceId, new FieldViewBinding(name, type, required));
//最后添加到set中,erasedTargetNames这个对象主要的作用是在后面
erasedTargetNames.add(enclosingElement);
}
这里也不是很复杂,也就是根据注解获取到的内容,判断变量是不是View的子类、不是static、不是private等等,然后获取名字、Id,然后用 FieldViewBinding
类包装一下
到这里其实已经根据所有有注解的内容已经按照 “类”
分好了,分好还不行,肯定会有什么BaseActivity、BaseDialog、BaseView所以要整合,让生成的类有联系
第二部分
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();
//拿队列出来的第一个 在erasedTargetNames中查询,父类是否在这个这个集合里
//为什么要这样? 因为可能 你在父类中 绑定了一个String
//在子类中使用了这个String,所以必须先初始化父类的String
//翻看生成好的代码来看,初始化都是在构造函数中进行绑定的
//所以考虑继承情况,必须把所有的类进行一个关联。
TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
if (parentType == null) {
//如果没有父类,则直接 放入
bindingMap.put(type, builder.build());
} else {
//如果父类有绑定
//再从bindingMap(即将返回的对象)中取看看 是否已经放进去了
BindingInformationProvider parentBinding = bindingMap.get(parentType);
if (parentBinding == null) {
//如果没绑定进去 再从classpathBindings中取 一个父类
parentBinding = classpathBindings.get(parentType);
}
if (parentBinding != null) {
//如果这个父类不是空的 则和当前循环里的builder 子、父类绑定
builder.setParent(parentBinding);
//放入即将返回的map里
bindingMap.put(type, builder.build());
} else {
//翻译是:有个超类绑定,但还没有构建它,放到后面继续排队
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
//比如:子类继承父类都有绑定,这时候子类需要关联父类,父类还没初始化,
//把子类放到队尾,等父类初始化完成在进行关联
entries.addLast(entry);
}
}
}
//返回,这时候这个map的key 就全是“类信息”
//value就是获取好的 当前类里面需要绑定的内容
//并且 “类” 也已经做好了继承关系
return bindingMap;
看着代码挺多,主要就2个部分
以View为例,收集View信息,规制到同一个类中,把有关联的类,关联起来
2、binding.brewJava(sdk, debuggable)
到这里就简单多了
Map<TypeElement, BindingSet> bindingMap =
照着这个bindingMap对象 遍历里面的“类”,生成文件即可
这里面用了另一个库 javapoet 专门用来生成类文件的
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(sdk, debuggable)
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) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
这里就不加注释了,这 PUBLIC、FINAL
下面的判断isView、isActivity、isDialog,点一个进去
private MethodSpec createBindingConstructorForActivity() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
builder.addStatement("this(target, target.getWindow().getDecorView())");
} else {
builder.addStatement("this(target, target)");
}
return builder.build();
}
这些 builder.addStatement
的内容,打开变异后的文件,都能看到
这里仅仅是生成文件的一些工具方法
最后就是 JavaFile.builder
建造者返回一个 build
对象
最后 javaFile.writeTo(filer);
生成文件
BindingSet
一个比较关键的类,是一个类里面,需要绑定的所有内容,比如String、View、Drawable等
代码太多,我就贴部分内容
一开始会有很多的 static final ClassName
声明的内容,ClassName
是 javapoet
里面的,都是生成文件需要的包名
成员变量:
//隐藏了看变量名就能看懂的 变量
private final ImmutableList<ViewBinding> viewBindings;
private final ImmutableList<FieldCollectionViewBinding> collectionBindings;
private final ImmutableList<ResourceBinding> resourceBindings;
private final @Nullable BindingInformationProvider parentBinding;
ImmutableList
:这是JDK提供的一些集合实现,当你不希望修改一个集合类,或者想做一个常量集合类可以使用它,当list用就行。
这里分成了3种情况进行绑定,BindView
、BindViews
、bindResource
BindView
绑定一个View ,ViewBinding
类是一个view的信息,生成 R文件
的常量,xml
里面 id
的名字,如果有 Listener
则会有事件的信息方法等等
BindViews
绑定一群View, FieldCollectionViewBinding
类是一群View的信息,里面有个List<Id>,还有个type也就是一群View的类型
bindResource
绑定资源,比如Anim、String、Drawable
肯定因为绑定,其实也就是赋值的代码不好重复,和 java库工程里没有View内容所以分开的
ResourceBinding
是个接口
FieldAnimationBinding
、FieldDrawableBinding
、FieldResourceBinding
、FieldTypefaceBinding
这4个类继承了 ResourceBinding
接口,就是让这几个类确定 最后生成类文件里的变量 怎样 编写赋值代码
interface ResourceBinding {
Id id();
boolean requiresResources(int sdk);
CodeBlock render(int sdk);
}
final class FieldDrawableBinding implements ResourceBinding {
//移除部分代码
@Override public CodeBlock render(int sdk) {
if (tintAttributeId.value != NO_RES_ID) {
return CodeBlock.of("target.$L = $T.getTintedDrawable(context, $L, $L)", name, UTILS, id.code,
tintAttributeId.code);
}
if (sdk >= 21) {
return CodeBlock.of("target.$L = context.getDrawable($L)", name, id.code);
}
return CodeBlock.of("target.$L = $T.getDrawable(context, $L)", name, CONTEXT_COMPAT, id.code);
}
}
render(int sdk)
根据sdk版本,具体生成出当前版本 Drawable
赋值方式
到这里基本知道了ButterKnife是如何帮我们完成findViewById的
学艺不精,如果内容有错误请及时联系我,我及时改正