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, 同时,可以看到.辅助类和我们的类是相同路径的.
所有的辅助类都是继承自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的类似的处理.所以接下来我们主要看如何生成辅助类
源码追踪,.有删减
找到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的源码里.
先来个注解处理器的文章
https://www.cnblogs.com/ganchuanpu/p/9020478.html
https://blog.csdn.net/xiayong1/article/details/85854725
主要是根据定义的注解. 拿到注解对应的参数.然后再把这些参数进行处理. 对于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; } }注解的原理.找一张 网上的图
这里 我们注意.主要工作就是用注解处理器.根据注解.生成辅助类.且辅助类和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; }接下来看如何把有注解得到的数据封装成辅助类.他这里处理了各种注解.我们先只看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. 处理父类绑定.
他遍历的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进行绑定.这里主要是根据注解创建类结构.看不太懂也没事.
继续回到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 发布