ButterKnife 框架结构
- butterknife:提供绑定的入口,传入view以及target目标
- butterknife-annotations:定义了一系列如:view,onclick等参数、方法、成员变量等类型的运行时注解信息
- butterknife-compiler:封装了注解处理器(核心)
- butterknife-reflect
- butterknife-runtime
1. 绑定流程
1.1 butterknife模块实现了绑定UI对象的入口
以绑定activity为例开始分析
-
ButterKnife.bind(Activity activity):通过bind方法将Activity以及Activity顶级View作为参数传入
-
其中butterknife会根据注解对象(即Acitivity)生成对应的ViewBinding类,这个生成的过程在注解处理器中说明,我们姑且称之为“绑定类”
-
通过反射返回绑定类的实例对象,即Unbinder对象(绑定类是继承自Unbinder的)
public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView();//1.1 获取Activity最顶级的view return bind(target, sourceView); } public static Unbinder bind(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass();//1.2 获取绑定对象的类名 //1.3 通过Activity的class名,获取类名为clsName + "_ViewBinding"的类构造器 Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } ... //1.9 通过反射创建绑定类的对象 return constructor.newInstance(target, source);//try-catch }
-
findBindingConstructorForClass:获取butterknife生成的Activity绑定类的构造器
- BINDINGS为构造器对应绑定类名的缓存集合
- 由BINDINGS可以看出绑定类是继承自Unbinder类的
- 查询对象绑定类时,如果查询不到,会不断递归向上查询父类的绑定类直到android原生类为止
//构造器缓存
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//1.4 有缓存则取缓存
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
//1.5 因为这个绑定的或者是不断递归向上的:查询不到对象绑定类时,会查询对象父类的绑定类
// 所以当查询对象为android原生类时,则会停止
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")|| clsName.startsWith("androidx."))
return null;
try {//1.6 获取对象对应的绑定类的构造器对象
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
//1.7 当获取不到时(不存在),则递归查询对象父类的绑定类
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
...
}
BINDINGS.put(cls, bindingCtor);// 1.8 存入缓存
return bindingCtor;
}
1.2 绑定类的结构
-
绑定类位于app\build\generated\source\apt\debug\com.haha.updateass.MainActivity_ViewBinding
- MainActivity_ViewBinding的构造函数刚好与上面反射创建绑定类对应
- 通过findRequiredView查询到Button资源,再通过castView转换为Activity对应的Button对象,添加点击监听
- 调用Unbinder对象的unbind方法时,将资源关联都置为null
// Generated code from Butter Knife. Do not modify! package com.haha.updateass; import android.support.annotation.CallSuper; import android.support.annotation.UiThread; import android.view.View; import android.widget.Button; import butterknife.Unbinder; import butterknife.internal.DebouncingOnClickListener; import butterknife.internal.Utils; import java.lang.IllegalStateException; import java.lang.Override; public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view2131165219; @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public MainActivity_ViewBinding(final MainActivity target, View source) { this.target = target; View view; view = Utils.findRequiredView(source, R.id.bt_load, "field 'btLoad' and method 'onViewClicked'"); target.btLoad = Utils.castView(view, R.id.bt_load, "field 'btLoad'", Button.class); view2131165219 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.onViewClicked(p0); } }); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.btLoad = null; view2131165219.setOnClickListener(null); view2131165219 = null; } }
-
findRequiredView:里面通过findViewById查找对应的资源
- 方法位于D:\github\butterknife-master\butterknife-runtime\src\main\java\butterknife\internal\Utils.java
public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); ... }
-
castView:将view强转为对应的class类型
- 方法位于D:\github\butterknife-master\butterknife-runtime\src\main\java\butterknife\internal\Utils.java
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) { return cls.cast(view);//try-catch ... }
看到这里会有疑问:绑定类中查找资源,强转资源类型都能理解,但是这些资源信息到底是如何生成的?接下来看注解处理器Butterknife-Processor
2. butterknife注解处理器
通过对Android注解处理器的学习,我们可以看到butterknife自定义的一个注解处理器ButterKnifeProcessor,他通过继承AbstractProcessor实现对应的方法。对于Android注解器的使用可以参考 link
2.1 注册注解器
-
@AutoService(Processor.class) 是google提供给我们的自动向javac注册注解器的方法,避免手动去配置注解器
@AutoService(Processor.class) @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC) @SuppressWarnings("NullAway") // TODO fix all these... public final class ButterKnifeProcessor extends AbstractProcessor { ... }
-
ButterKnifeProcessor继承AbstractProcessor,重写了几个重要方法
-
init(ProcessingEnvironment env) 里面判断了sdk版本,拿到了Types ,Filer ,Trees 等实例,其中Filer主要用作创建新文件,比如类文件
-
getSupportedOptions() 获取注解器支持的配置项
-
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<>(); //添加ButterKnife支持的注解类型 annotations.add(BindAnim.class); annotations.add(BindArray.class); annotations.add(BindBitmap.class); ... return annotations; }
-
process(Set<? extends TypeElement> elements, RoundEnvironment env) 核心方法,查询和处理注解,生成java文件
-
2.2 process
-
process方法查询所有带注解的节点并封装
@Override public boolean procesButterKnifes(Set<? extends TypeElement> elements, RoundEnvironment env) { //1. 查询获取所有带ButterKnife绑定注解类型的元素集合 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); //2. 通过查询到的bind元素,根据特定规则生成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 查询获取所有带ButterKnife bind注解类型的元素集合,以bindView为例
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); ... //省略部分节点如BindInt.class // 获取BindView 标记的Element for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { //解析BindView注解信息 parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } ...//中间有解析添加那些带监听的注解 Map<TypeElement, ClasspathBindingSet> classpathBindings = findAllSupertypeBindings(builderMap, erasedTargetNames); // 将builderMap的数据添加到队列 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) { //将TypeElement, BindingSet.Builder对应存入集合 bindingMap.put(type, builder.build()); } else { BindingInformationProvider parentBinding = bindingMap.get(parentType); if (parentBinding == null) { parentBinding = classpathBindings.get(parentType); } if (parentBinding != null) { //当有父类节点是,通过setParent记录到BindingSet builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // 具有父类绑定,但我们尚未构建它。 重新加入队列末尾 entries.addLast(entry); } } } return bindingMap; }
-
parseBindView 解析view注解的信息封装成BindingSet,与之TypeElement对应存储到集合中
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { //getEnclosingElement 返回封装此元素(非严格意义上)的最里层元素。 //可以理解上一级,父类的view元素 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); //验证当前fields的权限类型以及是否位andriod原生api或者成员 boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // 验证目标类型是否从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(); if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) { ...//判断当前元素是否是View的子类,或者是接口,不是的话抛出异常 } if (hasError) return; //解析View的属性值,这里是view的id int id = element.getAnnotation(BindView.class).value(); //查询是否存在父类以及绑定的情况 BindingSet.Builder builder = builderMap.get(enclosingElement); Id resourceId = elementToId(element, BindView.class, id); if (builder != null) { ... //存在View的父类以及绑定,则校验此id是否被绑定,如果已绑定则抛出异常 } else { //创建BindingSet.Builder,将enclosingElement存入builderMap builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); //将元素对应的信息存入到builder中 builder.addField(resourceId, new FieldViewBinding(name, type, required)); // 将view的父类元素存入erasedTargetNames集合 erasedTargetNames.add(enclosingElement); }
-
BindingSet :封装了bind类的注解元素信息,用来生成特定的类
private BindingSet( TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement, boolean isFinal, boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings, ImmutableList<FieldCollectionViewBinding> collectionBindings, ImmutableList<ResourceBinding> resourceBindings, @Nullable BindingInformationProvider parentBinding) { this.isFinal = isFinal; this.targetTypeName = targetTypeName; this.bindingClassName = bindingClassName; this.enclosingElement = enclosingElement; this.isView = isView; this.isActivity = isActivity; this.isDialog = isDialog; this.viewBindings = viewBindings; this.collectionBindings = collectionBindings; this.resourceBindings = resourceBindings; this.parentBinding = parentBinding; }
3. 生成绑定类
里面用到了JavaPoet技术具体参考https://blog.csdn.net/l540675759/article/details/82931785
在上述process中拿到BindingSet后通过其brewJava(sdk, debuggable)方法来生成JavaFile对象,
而生成JavaFile的方法则是通过JavaPoet开源库完成。
- brewJava
JavaFile brewJava(int sdk, boolean debuggable) {
//TypeSpec 包含了类,接口,方法声明
//createType则对应生成TypeSpec信息
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
4. 结尾
ButterKnife总体的框架原理很简单,首先生成对应的绑定类,然后通过反射完成绑定类的实例化和注册。但是根据注解对应去生成绑定类,里面封装了太多的细节和内容。其中用到了annotationProcessor 和JavaPoet 技术,当然里面也很有很多值得学习的地方,每一个好的开源框架里面不可少的缓存机制和模式使用。