Butterknife是一款利用注解方式实现的框架,为Android项目提供了较好的解耦。下面针对这款大神作品,做下简单分析。
核心成员介绍
1.Butterknife-annotations
注解库,里面包含了所有用到的注解类
例如BindView.java
@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
2.Butterknife-compiler
负责在编译过程中生成view_Binding文件
核心成员类 ButterKnifeProcessor.java
我们来看一下这个类的源代码
public final class ButterKnifeProcessor extends AbstractProcessor
这个类继承了AbstractProcessor,正是利用了apt的原理,如对apt不了解,建议先百度学习一下。
接下来再看下ButterKnifeProcessor的核心方法
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk, debuggable, useLegacyTypes);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
通过代码可以发现process过程包含了以下操作
- 通过编译期查找已经在代码中添加注解标识的类,得到binding元素的Map
binding注解过的元素例如
@BindView(R.id.textview)
TextView textview
@OnClick(R.id.click)
void onClick(View view)
等等。
- 从得到的Map中挨个取出每个BindingSet
- 根据binding对象生成JavaFile对象,然后写入文件(xxx_viewBinding.java)
ps:简单看下两个过程
- findAndParseTargets查找过程
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
……
省略一部分代码
……
// 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);
}
}
// Process each @BindViews element.
for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindViews(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindViews.class, e);
}
}
……
省略一部分代码
……
}
这里面实际上是分别对@BindView, @BindViews, @BindString等类型做了查找
- BindingSet的生成JavaFile过程(brewJava)
JavaFile brewJava(int sdk, boolean debuggable, boolean useLegacyTypes) {
TypeSpec bindingConfiguration = createType(sdk, debuggable, useLegacyTypes);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
这里通过createType()方法得到了TypeSpec对象,然后再通过建造者模式创建了JavaFile对象,并返回。
那么,再看下createType()具体做了什么
private TypeSpec createType(int sdk, boolean debuggable, boolean useLegacyTypes) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
if (isView) {
result.addMethod(createBindingConstructorForView(useLegacyTypes));
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity(useLegacyTypes));
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog(useLegacyTypes));
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor(useLegacyTypes));
}
result.addMethod(createBindingConstructor(sdk, debuggable, useLegacyTypes));
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result, useLegacyTypes));
}
return result.build();
}
这里主要是定义了即将创建的viewBinding类的属性,并且,所有的类都实现了Unbinder接口。这点很重要,因为后面绑定时,得到的正是这个Unbinder对象。
3.Butterknife
接下来看下绑定时用到的操作类。
当我们要绑定控件时,可以在组件的生命周期开始的时候调用Butterknife.bind()方法
例如
public class TestActivity extends Activity {
@Override
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
unbinder = ButterKnife.bind(this);
}
}
那看下ButterKnife的bind()方法具体做了什么
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
实际上获取了Activity的根View,也就是decorView。
再往下看
@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());
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);
} 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的newInstance()方法得到Unbinder对象并返回
接下来就看findBindingConstructorForClass()
很显然,这一步操作跟之前Butterknife-compiler里面生成的viewBinding文件有关,那么,我们来看代码
@VisibleForTesting
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) {
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 {
Class<?> bindingClass = cls.getClassLoader().loadClass(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;
}
这里有3步操作
- 从BINDINGS缓存中读取Unbinder的构造器对象,BINDINGS是个LinkedHashMap,很好的满足缓存功能,若缓存中已有,则表示已经加载过。
- 缓存中没有,则通过传进来的Class对象(例如TestActivity.class)调用类加载器ClassLoader的loadClass()方法得到TestActivity_ViewBinding.class对象。这个TestActivity_ViewBinding.class也就是在编译时生成的binding文件生成的。
- 将加载出来的ViewBinding的class实例放入到缓存中
由此一来,就完成了绑定操作
总结
总结来说,分为两大部分,编译时,和运行时发生的操作。
- 编译时通过ButterKnifeProcessor生成ViewBinding文件
- 运行时,通过ButterKnife.bind()加载ViewBinding文件并绑定得到Unbinder对象
转载请注明出处,谢谢合作。作者,皮卡丘
附上一只大猫图
网友评论