引言:
本文基于ButterKnife当前的最新版本(10.2.1)(部分内容借鉴于https://www.jianshu.com/p/100708625605,感谢Darren),ButterKnife这个框架,应该很多人都不会陌生,这个框架帮我们省略了自己写findViewById,setOnClickListener...等等步骤,是一个比较知名的优秀框架之一。那么,它的原理是怎样呢?
![](https://img.haomeiwen.com/i10133692/2bbf704b63d3d2bd.png)
可以看到,作者将主要功能其分成了几个部分,butterknife-annotations、butter-compiler、butterknife,他为什么要这样分呢?让我们先看下他的每个部分是干什么的。
butterknife-annotations
是一个javaLib(为什么用javaLib,而不是用AndroidLib,因为不需要,哈哈,废话...),主要是存放annotation的
butterknife-compiler
是一个javaLib(为什么用javaLib,因为要用java的注解处理类AbstractProcessor,这个类javaLib才有,AndroidLib没有),butterknife-compiler主要是做注解处理,并在编译时期生成XXX_ViewBinding的类和代码,如果在Activity中使用,这个XXX就是Activity的类名。生成的代码如下:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.tv1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv1'", TextView.class);
target.tv2 = Utils.findRequiredViewAsType(source, R.id.tv2, "field 'tv2'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.tv1 = null;
target.tv2 = null;
}
butterknife
是一个androidLib,butterknife主要职责是通过反射初始化生成的XXX_ViewBinding类,如果是在Activity中使用,就是将当前的Activity作为参数传入XXX_ViewBinding的构造方法。主要代码:
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
}
@Nullable
@CheckResult
@UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding")
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e)
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
小结:作者将其分成几个lib的主要原因应该是butterknife-compiler主要职责处理注解和生成代码,需要使用annotationProcessor,来添加依赖,butterknife-compiler在运行时不会被使用,所以需要把它分出来,符合单一职责思想。annotationProcessor仅仅是在编译期使用,并不会打包到apk中。butterknife-annotations里面的注解在项目需要依赖,butterknife-compiler也需要依赖,所以作者也将其分了出来。butterknife是反射获取XXX_ViewBinding,项目需要依赖,butterknife-compiler不需要依赖,所以也分了lib出来。
关于手写ButterKnife
此处并非要去重复造轮子,主要是为了更好的吸取作者的一些好的编程思想,以运用到工作中,仅供学习用途。接下来让我们开始吧。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
1.新建javaLib,butterknife-annotations,写好第一个编译期注解BindView
2.新建javaLib,butterknife-compiler,导入依赖 javapoet和auto-service,使用javapoet生成java类,auto-service在这里的作用是自动让IDE生成META-INF,表明我们接下来写的ButterKnifeProcessor是一个注解处理类。
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' //gradle3.0以上版本需要
implementation 'com.squareup:javapoet:1.12.1'
3.butterknife-compiler新建ButterKnifeProcessor继承自AbstractProcessor,使用@AutoService(Processor.class)注解。
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
private Filer mFiler;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
}
@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(BindView.class);
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
4.ButterKnifeProcessor内处理BindView注解和生成代码
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取所有BindView注解的view
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//创建map key -> activity value -> activity下所有BindView注解的view
Map<Element, List<Element>> elementListMap = new LinkedHashMap<>();
for (Element element : elements) {
//activity
Element enclosingElement = element.getEnclosingElement();
List<Element> elementList = elementListMap.get(enclosingElement);
if (elementList == null) {
elementList = new ArrayList<>();
elementListMap.put(enclosingElement, elementList);
}
elementList.add(element);
}
//遍历elementListMap
for (Map.Entry<Element, List<Element>> entry : elementListMap.entrySet()) {
Element element = entry.getKey();
List<Element> viewElements = entry.getValue();
String activityClassNameStr = element.getSimpleName().toString();
ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
ClassName unBinderClassName = ClassName.get("com.butterknife", "Unbinder");
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(unBinderClassName)
.addField(activityClassName, "target", Modifier.PRIVATE);
//构造函数
ClassName UIThreadAnnotationClassName = ClassName.get("androidx.annotation",
"UiThread");
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addAnnotation(UIThreadAnnotationClassName)
.addParameter(activityClassName, "target")
.addModifiers(Modifier.PUBLIC)
.addCode("this(target, target.getWindow().getDecorView());");
ClassName viewClassName = ClassName.get("android.view", "View");
MethodSpec.Builder constructorMethodBuilder2 = MethodSpec.constructorBuilder()
.addAnnotation(UIThreadAnnotationClassName)
.addParameter(activityClassName, "target")
.addParameter(viewClassName, "source")
.addModifiers(Modifier.PUBLIC)
.addCode("this.target = target;\n");
ClassName utilsClassName = ClassName.get("com.butterknife", "Utils");
for (Element viewElement : viewElements) {
String fieldName = viewElement.getSimpleName().toString();
int resId = viewElement.getAnnotation(BindView.class).value();
String type = viewElement.asType().toString();
constructorMethodBuilder2.addCode("target.$L = $T.findRequiredViewAsType(source," +
" $L, \"field '$L'\", $L.class);\n", fieldName, utilsClassName, resId, fieldName, type);
}
//接口方法
ClassName callSuperAnnotationClassName = ClassName.get("androidx.annotation",
"CallSuper");
MethodSpec.Builder interfaceMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addAnnotation(callSuperAnnotationClassName)
.addModifiers(Modifier.PUBLIC)
.addCode("$L target = this.target;\n", activityClassName)
.addCode("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");\n")
.addCode("this.target = null;\n");
for (Element viewElement : viewElements) {
String fieldName = viewElement.getSimpleName().toString();
interfaceMethodBuilder.addCode("target.$L = null;\n", fieldName);
}
classBuilder.addMethod(constructorMethodBuilder.build());
classBuilder.addMethod(constructorMethodBuilder2.build());
classBuilder.addMethod(interfaceMethodBuilder.build());
String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
try {
JavaFile.builder(packageName, classBuilder.build())
.build().writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
5.新建butterknife,AndroidLib,反射调用生成的XXX_ViewBinding构造方法,传入参数。
public class ButterKnife {
private ButterKnife() {
throw new AssertionError("No instances.");
}
private static final String TAG = "ButterKnife";
private static boolean debug = false;
@VisibleForTesting
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@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();
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 (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);
}
}
@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 {
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;
}
}
网友评论