ButterKinfe的工作原理
1.编译时扫描注解,并作相应处理,使用javapoet库生成Java代码
2.调用ButterKnife.bind(this)方法时候将id和上下文绑定在一起
ButterKinfe使用编译时注解的方式完成Android控件和点击事件的绑定,不用重复的去写findViewById和setOnClicListener等代码,使用方法非常简单,看官方文档 http://jakewharton.github.io/butterknife/
关于注解的相关知识可以看这篇文章https://blog.youkuaiyun.com/Icarus_/article/details/103685601
clone项目到本地 https://github.com/JakeWharton/butterknife
用Android studio打开项目查看源码
1、注解定义
先从@BindView()
方法开始看
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
可以看出注解可以保留到class文件中,修饰的是成员变量,定义了一个value()方法,表示只用此注解时候可以接收一个int型的参数,使用@IdRes注解表示参数是一个资源id。
2、注解解析
在butterknife-compiler中,定义了注解处理器ButterKnifeProcessor类
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
......
}
使用@AutoService(Processor.class)注册注解解析器,没有使用@SupportedSourceVersion声明所支持的jdk版本,@SupportedAnnotationTypes声明该注解处理器想要处理那些注解。而是使用getSupportedSourceVersion方法返回支持的Java版本,getSupportedAnnotationTypes方法返回支持的注解类型。
//返回支持的注解类型
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
//返回支持的Java版本
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
跟进去看一下getSupportedAnnotations,指定ButterKnifeProcessor是注册给哪些注解的
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;
}
ButterKnifeProcessor类通过process方法来完成注解的解析
首先通过 findAndParseTargets方法获取所有注解,并保存到map中,map中的key是TypeElement类型,表示被注解的类,value是BindingSet类型,表示待生成的类所需的所有信息(如包名,类名,绑定的控件等)。
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
之后遍历这个bindingMap,使用BindingSet对象创建一个JavaFile对象,进而生成Java文件。
process代码如下
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//获取所有注解元素并解析,返回map
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//遍历集合所有数据
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//根据BindingSet 生成一个JavaFile 对象
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
//生成Java源文件
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
分析一下findAndParseTargets方法是如何解析注解的,该方法生成一个Map<TypeElement, BindingSet>包含所有带生成的Java文件需要的信息,创建 Map<TypeElement, BindingSet.Builder>解析所有注解完成builderMap 初始化,最后使用builderMap 完成bindingMap的创建并返回,代码如下。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
//builderMap集合是为了完成Map<TypeElement, BindingSet>集合的构建
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
......
//还有好多类似的解析代码,这里就用BindView举例
// 解析被 @BindView 注解的元素
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);
}
}
......
return bindingMap;
}
他其实是针对每一个自定义的注解,比如上面所说的BindView,BindBitmap等做了处理,选取一个跟进去看一下parseBindView方法。
parseBindView方法会对被注解的元素做可访问检查,包名检查,类型检查,被绑定的元素必须继承View类或者一个接口,然后从builderMap集合中获取被注解的元素所在类的对应的BindingSet.Builder对象,如果没有,调用getOrCreateBindingBuilder创建一个对象,再存入集合中。
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
//获取注解@BindView所在类的TypeElement对象
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 首先验证常见的生成代码限制。
//1.isInaccessibleViaGeneratedCode方法,如果注解的View被private或者static修饰,
//或者不在一个类中,或在private修饰的类中,返回false
//2.isBindingInWrongPackage方法,注解的类包名以android.或者以java.开头,返回false
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
//TypeElement一些父类信息获取不到,要通过他来获取。asType方法用来验证类型是否继承自view
TypeMirror elementType = element.asType();
......
//判断被注解的元素是否为view的子类或者接口
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
......
}
//获取被@BindView注解的控件所在类对应BinderSet.Builder对象
int id = element.getAnnotation(BindView.class).value();
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 {
//获取或者创建一个FindViewBindingBuilder对象,为生成代码做准备
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
......
//添加到map集合中
builder.addField(resourceId, new FieldViewBinding(name, type, required));
}
接着看一下getOrCreateBindingBuilder方法
private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
//从builderMap中获取注解元素所在类对应的BindingSet.Builder对象
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
//没有则创建
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
看一下addField方法,里面调用了getOrCreateViewBindings方法,将id和view进行绑定
void addField(Id id, FieldViewBinding binding) {
getOrCreateViewBindings(id).setFieldBinding(binding);
}
private ViewBinding.Builder getOrCreateViewBindings(Id id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}
3、生成Java文件
BindingSet对象准备好后,就可以使用BindingSet对象生成一个Java文件,在ButterKnifeProcessor的process方法中调用BindingSet对象的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();
}
这里是使用了JavaPoet库,主要调用createType方法完成创作
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC)
.addOriginatingElement(enclosingElement);
if (isFinal) {
//是否是final,添加修饰符
result.addModifiers(FINAL);
}
//parentBinding把绑定的集合归在一个集合当中
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());
}
//如果构造方法不需要view参数,就需要添加一个view参数的构造方法
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
//创建带2个参数构造方法,这个方法就能完成把注解换算成findViewById代码
result.addMethod(createBindingConstructor(sdk, debuggable));
//创建解绑方法
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
生成的创建带一个参数的构造方法
@UiThread
public ShareDialogActivity_ViewBinding(ShareDialogActivity target) {
this(target, target.getWindow().getDecorView());
}
对应的createBindingConstructorForActivity方法为
private MethodSpec createBindingConstructorForActivity() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)//添加注解@UiThread
.addModifiers(PUBLIC)//添加public修饰符
.addParameter(targetTypeName, "target");//添加参数target
if (constructorNeedsView()) {
builder.addStatement("this(target, target.getWindow().getDecorView())");
} else {
builder.addStatement("this(target, target)");
}
return builder.build();
}
创建带2个参数的构造方法
@UiThread
public ShareDialogActivity_ViewBinding(ShareDialogActivity target, View source) {
this.target = target;
target.mRecyclerView = Utils.findRequiredViewAsType(source, R.id.share_dialog_recyclerview, "field 'mRecyclerView'", RecyclerView.class);
target.mBtnClose = Utils.findRequiredViewAsType(source, R.id.btn_close, "field 'mBtnClose'", ImageView.class);
}
对应的createBindingConstructor方法为
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
//创建构造方法的参数target
constructor.addParameter(targetTypeName, "target");
}
if (constructorNeedsView()) {
//创建构造方法的参数source
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}
......
//创建语句this.target=target,换行
if (hasTargetField()) {
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
//创建绑定控件代码
for (ViewBinding binding : viewBindings) {
//创建语句Utils.findRequiredViewAsType
addViewBinding(constructor, binding, debuggable);
}
......
}
......
return constructor.build();
}
4、绑定bind
在butterkinfe模块下有个ButterKinfe类,里面是绑定的方法,比如看bind(Activity target)这个重载方法,根据传入的activity名字,找到对应生成的以_ViewBinding结尾的类,使用类加载器加载,通过反射调用构造方法,最后调用获取到的构造方法完成注解元素的绑定
@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());
//找到对应生成的以_ViewBinding结尾的类
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
......
try {
//调用构造方法
return constructor.newInstance(target, source);
}
......
}