ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 概述 * 编写代码时对目标元素进行注解 * 编译时注解处理器会扫描注解,并通过 JavaPoet 自动生成 Java 文件 * 调用 ButterKnife.bind()方法时,会通过反射拿到之前为目标类生成的 TargetClassName_ViewBinding 类,并调用其构造方法,完成绑定 # 具体分析 ## 源码组成 ButterKnife 的源码主要的组成部分包括三个:butterknife、butterknife-annotations、butterknife-compiler。其中 butterknife-annotations 声明了所有的注解;butterknife-compiler 为注解处理器,在编译阶段对注解进行处理;butterknife 则是我们调用的接口。接下来我们一一来看。 ## butterknife-annotations ButterKnife 提供了如下注解: * 绑定类型的注解:BindAnim、BindArray、BindBitmap、BindBool、BindColor、BindDimen、BindDrawable、BindFloat、BindFont、BindInt、BindString、BindView、BindViews * 监听类型注解:OnCheckedChanged、OnClick、OnEditorAction、OnFocusChange、OnItemClick、OnItemLongClick、OnItemSelected、OnLongClick、OnPageChange、OnTextChanged、OnTouch、Optional ## butterknife-compiler 复习下在 EventBus 源码分析时用到的注解处理器: 注解处理器用来在编译时扫描和处理注解,会自动生成 .java 文件。 每一个注解处理器都继承于 AbstractProcessor, ```java package com.example; public class MyProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment env){ } @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { } @Override public Set<String> getSupportedAnnotationTypes() { } @Override public SourceVersion getSupportedSourceVersion() { } } ``` 其中必需的几个方法如下: * init(ProcessingEnvironment env):会被外部的工具调用,传入 ProcessingEnvironment 参数,我们可以从其中获取到一些工具类 * process():在这里扫描、处理注解以及生成代码 * getSupportedAnnotationsTypes():在这里定义本注解处理器会注册到哪些注解上 * getSupportedSourceVersion():指定 Java 版本 那么首先来看看在 butterknife-compiler 中的关键类:ButterknifeProcessor。 ```java public final class ButterKnifeProcessor extends AbstractProcessor { // 初始化工具 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); ... elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.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(BindColor.class); annotations.add(BindView.class); ... annotations.addAll(LISTENERS); return annotations; } // 对注解进行处理 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {} } ``` ### process 方法 接下来我们重点看看 process 方法,也就是注解处理器是如何对注解进行操作的。 ```java // 对注解进行处理 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { // 扫描注解并解析,得到 Map Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); // 生成 Java 文件 for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; } ``` 通过参数 RoundEmviroment 我们可以拿到所有包含特定注解的被注解元素,生成一个 Key 为 TypeElement,Value 为 BindingSet 的 Map;接着遍历 Map 生成相应的 Java 文件。 先来看看扫描注解并解析的源码: ```java private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { ... // Process each @BindAnim element. // 遍历所有被注解的元素 for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) { try { parseResourceAnimation(element, builderMap, erasedTargetNames); } catch (Exception e) { ... } } ... // Process each annotation that corresponds to a listener. for (Class<? extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } // 把之前的 builderMap 转换为 bindingMap ... 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); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.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; } ``` 中间省略了一些注解的处理,每种注解处理方式基本一致,以 BindAnim 为例,其中的关键方法为 parseResourceAnimation,其他注解的该方法分别对应着(parseBindView、parseResourceString 等) ```java private void parseResourceAnimation(Element element, ... // Assemble information on the field. BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource(new FieldAnimationBinding(getId(qualifiedId), name)); erasedTargetNames.add(enclosingElement); } ``` parseResourceAnimation 的作用为生成被注解元素所对应类的 Builder,并得到 builderMap。 接下来看看生成 Java 文件的代码: ```java for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } ``` 这里使用 JavaPoet 生成了代码。 源码分析未完待续! 来看看 JavaPoet 为我们生成的 Java 文件(示例中 MainActivity 中有两个点击事件绑定): ```java public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view2131230756; private View view2131230758; @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public MainActivity_ViewBinding(final MainActivity target, View source) { this.target = target; View view; view = Utils.findRequiredView(source, R.id.btn_alipay, "method 'onViewClicked'"); view2131230756 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.onViewClicked(p0); } }); view = Utils.findRequiredView(source, R.id.btn_custom_view, "method 'onViewClicked'"); view2131230758 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.onViewClicked(p0); } }); } @Override @CallSuper public void unbind() { if (target == null) throw new IllegalStateException("Bindings already cleared."); target = null; view2131230756.setOnClickListener(null); view2131230756 = null; view2131230758.setOnClickListener(null); view2131230758 = null; } } ``` 在构造方法中,可以看到我们熟悉的点击事件,View 的绑定同理。 ## butterknife 在 ButterKnife 的标准用法中,会在 Activity 或 Fragment 的 onCreate 方法中调用 `ButterKnife.bind(this);` 进行绑定,我们就首先从 bind 方法入手。 ```java public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); } private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass(); Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { ... } } ``` 可以看到,通过反射首先拿到 target 的类名,然后找到其对应的构造器,接着创建相应的对象。其中的 findBindingConstructorForClass 方法源码如下: ```java private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null) { return bindingCtor; } String clsName = cls.getName(); try { Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); //noinspection unchecked bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { } catch (NoSuchMethodException e) { } BINDINGS.put(cls, bindingCtor); return bindingCtor; } ``` 可以看到,根据类名拿到 targetClassName_ViewBinding 类的构造器,并调用其构造方法,进行绑定 # 参考文档 [ButterKnife 源码分析](https://www.jianshu.com/p/1c449c1b0fa2)