title: butterKnife源码解析
date: 2020-06-30 09:43:23
tags: [源码笔记]
typora-copy-images-to: ./imgs
typora-root-url: ./imgs
使用
使用就很简单了.导包.然后来个demo
private static final String TAG = "MainActivity";
private ViewGroup.LayoutParams layoutParams;
@BindView(R.id.tip)
TextView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.tip)
void onclick(View view ){
Toast.makeText(this, "uytut", Toast.LENGTH_SHORT).show();
}
原理
它主要是通过注解主动生成了一个辅助类,在辅助类中把Id和控件进行绑定。辅助类的后缀是_ViewBinding, 同时,可以看到.辅助类和我们的类是相同路径的.
![](https://img.haomeiwen.com/i5423393/6a4123f8c67718ce.png)
辅助类代码
所有的辅助类都是继承自unbinder. 他的unbind解绑操作必须要我们调用.否则会内存泄漏
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f0700b7;
@UiThread 构造函数.执行绑定过程
public MainActivity_ViewBinding(MainActivity target) {
//可以看到.这里拿到了 target的顶层view
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
//这里是的decorview 的findviewById.找到 id对应的view. 这个R.id.tip 就是我们之前@BindView(R.id.tip)的参数.
view = Utils.findRequiredView(source, R.id.tip, "field 'view' and method 'onclick'");
//把这个view 转换成TextView. 然后赋值给targetActivity 的view
target.view = Utils.castView(view, R.id.tip, "field 'view'", TextView.class);
view7f0700b7 = view;
设置点击事件.绑定 view的点击和 activity里的 onclick方法.这个方法可以随便命名.
同时这里巧妙的设置了放置瞬间多次点击的问题.
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onclick(p0);
}
});
}
@Override
@CallSuper 这个类里边因为绑定了activity和view. 需要主动释放.否则造成内存泄露
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.view = null;
view7f0700b7.setOnClickListener(null);
view7f0700b7 = null;
}
}
总结
这个辅助类.完成了activity和里边view 的绑定方法.并实现了点击的处理. 其实就是省略的我们手写的findviewById. 而butterknife 里主要的就是如何生成这个辅助类.和对不同view的类似的处理.所以接下来我们主要看如何生成辅助类
ButterKinfe.bind过程
源码追踪,.有删减
找到docerview, decorview是每个activity的顶层view
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
拿到target的class,因为这时候已经生成了辅助类.而辅助类和target类是在同样的路径下.只是比target类名称多了_ViewBinding
所以这里利用target的class来找到辅助类.然后调用构造函数初始化
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
try {
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
}
}
继续看,如何找到辅助类
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
先从LinkedHashMap缓存中查找
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
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名称找到class .然后就是得到构造函数.加入map缓存.
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
总结
这里也很明了. 通过target的class.找到生成的辅助类.然后反射创建辅助类.执行构造函数. 在构造函数中执行绑定.
生成辅助类
这里是在编译的时候通过编译时期-注解处理器.生成的辅助类. 这个代码在butterknife的源码里.
先来个注解处理器的文章
主要是根据定义的注解. 拿到注解对应的参数.然后再把这些参数进行处理. 对于butterkinfe就是生成辅助类.
public class MyProcessor extends AbstractProcessor {
//用来指定你使用的 java 版本。通常你应该返回 SourceVersion.latestSupported()
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//会被处理器调用,可以在这里获取Filer,Elements,Messager等辅助类,后面会解释
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
//这个方法返回stirng类型的set集合,集合里包含了你需要处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add("com.example.MyAnnotation");
return annotataions;
}
//核心方法,这个一般的流程就是先扫描查找注解,再生成 java 文件
//这2个步骤设计的知识点细节很多。
@Override
public boolean process(Set<? extends TypeElement> annoations,
RoundEnvironment env) {
return false;
}
}
注解的原理.找一张 网上的图
![](https://img.haomeiwen.com/i5423393/e29829e2afa51142.png)
![](https://img.haomeiwen.com/i5423393/0b4a54a80f7b0a8f.png)
这里 我们注意.主要工作就是用注解处理器.根据注解.生成辅助类.且辅助类和target在同样的路径中.这里有手动生成java文件的操作.
主要代码在ButterKnifeProcessor 类中.在 butterkinfe-compiler 模块中.
Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。注解处理器运行在它自己的 JVM 中
下面看源码
支持的注解
这是他支持的所有注解类型.其实一目了然.
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) {
得到 BindingSet 集合.这里是对各种注解的处理.
bindingSet 可以理解为是一个java 文件的抽象.他里边的各种属性最后可以生成一个完整的java文件.这是butterknife自己定义的.
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//这里已经生成了java文件,这个java文件就是上文的辅助类.
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
把java文件由内存中写到磁盘上
javaFile.writeTo(filer);
} catch (IOException e) {
}
}
return false;
}
接下来看如何把有注解得到的数据封装成辅助类.他这里处理了各种注解.我们先只看BindView
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
这里是拿到所有 bindView 的注解.然后遍历处理.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
对lister的处理.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, 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();
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;
}
这里总共看三步. 处理bindview的注解.生成对应bindingset. 处理listener. 处理父类绑定.
解析bindView注解
他遍历的Element 代表了源文件中的每一部分特定类型.介绍如下
public class ClassA { // TypeElement
private int var_0; // VariableElement
public ClassA() {} // ExecuteableElement
public void setA( // ExecuteableElement
int newA // TypeElement
) {
}
}
入口处在 ButterKnifeProcessor.parseBindView
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
这里就是注解类型
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
TypeMirror elementType = element.asType();
注解的名称和全限定名
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
// 拿到注解对应的id值. 就是我们写的BindView(R.id.tip) 中的R.id.tip
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
封装id并缓存
Id resourceId = elementToId(element, BindView.class, id);
这里创建了新的 BindingSet .为了封装 bindview 的相关参数.
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(resourceId);
} else {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
到这.就给辅助类生成了这个 bindview注解先关的属性.名称.类型.然后添加到builder中.最后统一创建.
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
我猜其实就是生成 private View view7f0700b7; 这种类型的格式.当然还绑定了对应的id.
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
所以这方法大概就是,通过annotation的类型.然后取出注解的值. 最后经过封装.创建这个属性的builder.与id进行绑定.这里主要是根据注解创建类结构.看不太懂也没事.
listener的处理
继续回到ButterKnifeProcessor 的findAndParseTargets中对点击事件的处理
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
接着看 findAndParseListener 又跳到parseListenerAnnotation.
private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
先拿到注解的类型.
ExecutableElement executableElement = (ExecutableElement) element;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
在拿到注解中的值.也就是绑定点击的id数组. @OnClick({R.id.hello,R.id.hello2}) 这个
int[] ids = (int[]) annotationValue.invoke(annotation);
String name = executableElement.getSimpleName().toString();
boolean required = isListenerRequired(executableElement);
这里我也看不太懂. 应该是监听的注解. 这里我们要知道.butterknife会默认创建一个listener.并在click中执行view的回调
ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
ListenerMethod method;
ListenerMethod[] methods = listener.method();
callback 的注解. 没有listener的就会走callback 的回调
Method annotationCallback = annotationClass.getDeclaredMethod("callback");
Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
Field callbackField = callback.getDeclaringClass().getField(callback.name());
method = callbackField.getAnnotation(ListenerMethod.class);
拿到所有参数
List<? extends VariableElement> methodParameters = executableElement.getParameters();
返回值类型
TypeMirror returnType = executableElement.getReturnType();
最后,把上边的参数进行封装.等着以后生成listener的辅助类.
MethodViewBinding binding =
new MethodViewBinding(name, Arrays.asList(parameters), required, hasReturnValue);
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
Map<Integer, Id> resourceIds = elementToIds(element, annotationClass, ids);
看一下生成listener相关的注解,应该就是构建这个类似的结构.
@ListenerClass(
targetType = "android.widget.AdapterView<?>",
setter = "setOnItemClickListener",
type = "android.widget.AdapterView.OnItemClickListener",
method = @ListenerMethod(
name = "onItemClick",
parameters = {
"android.widget.AdapterView<?>",
"android.view.View",
"int",
"long"
}
)
)
最后就是那这些封装数据.生成一个java类. 那个不看了.不好理解.
本文由博客群发一文多发等运营工具平台 OpenWrite 发布
网友评论