算是一款知名老牌 Android 开发框架了,通过注解绑定视图,避免了 findViewById() 的操作,广受好评!由于它是在编译时对注解进行解析完成相关代码的生成,所以在项目编译时会略耗时,但不会影响运行时的性能。接下来让我们从使用到原理一步步深入了解这把黄油刀的故事!
以下内容基于 butterknife:8.8.1
版本,主要包括如下几个方面的内容:
- 简单使用
- 原理分析
- 注解处理器
- JavaPoet
一、简单使用
首先编写一个 ButterKnife 简单使用的例子,方便后续的分析。先在 app的 build.gradle 中加入如下配置,完成 ButterKnife 引入:
dependencies { ...... implementation 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'}复制代码
接下来在 Activity 中使用,界面上一个TextView
一个Button
,很简单就不解释了:
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv_title) TextView title; @OnClick(R.id.bt_submit) public void submit() { title.setText("hello world"); } private Unbinder unbinder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); unbinder = ButterKnife.bind(this); } @Override protected void onDestroy() { unbinder.unbind(); super.onDestroy(); }}复制代码
二、原理分析
最后编译一下项目。直觉告诉我们应该从ButterKnife.bind(this)
开始分析,因为它像是 ButterKnife 和 Activity 建立绑定关系的过程,看具体的代码:
@NonNull @UiThreadpublic static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView);}复制代码
sourceView
代表当前界面的顶级父 View,是一个FrameLayout
,继续看createBinding()
方法:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); Constructor constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } // 省略了相关异常处理代码 }复制代码
首先得到要绑定的 Activity 对应的 Class,然后用根据 Class 得到一个继承了Unbinder
的Constructor
,最后通过反射constructor.newInstance(target, source)
得到Unbinder
子类的一个实例,到此ButterKnife.bind(this)
操作结束。这里我们重点关注findBindingConstructorForClass()
方法是如何得到constructor
实例的:
@Nullable @CheckResult @UiThread private static Constructor findBindingConstructorForClass(Class cls) { Constructor 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.")) { 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 ) 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; }复制代码
整体的流程是先检查BINDINGS
是否存在 Class 对应的 Constructor,如果存在则直接返回,否则去构造对应的 Constructor。其中BINDINGS
是一个LinkedHashMap
: Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>()
,缓存了对应的 Class 和 Constructor 以提高效率! 接下来看当不存在对应 Constructor 时如何构造一个新的,首先如果clsName
是系统相关的,则直接返回 null,否则先创建一个新的 Class:
Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");复制代码
这里新的 Class 的 name 是 com.shh.sometest.MainActivity_ViewBinding
,最后用新的 Class 创建一个 继承了Unbinder
的 Constructor,并添加到BINDINGS
:
bindingCtor = (Constructor ) bindingClass.getConstructor(cls, View.class);BINDINGS.put(cls, bindingCtor);复制代码
所以最终bind()
方法返回的是MainActivity_ViewBinding
类的实例。既然可以返回MainActivity_ViewBinding
的实例,那MainActivity_ViewBinding
这个类肯定是存在的。可以在如下目录找到它(这个类是在项目编译时期由 annotationProcessor 生成的,关于 annotationProcessor 后边会说到):
public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view2131165217; @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; target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class); view = Utils.findRequiredView(source, R.id.bt_submit, "method 'submit'"); view2131165217 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.submit(); } }); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.title = null; view2131165217.setOnClickListener(null); view2131165217 = null; }}复制代码
之前createBinding()
方法中return constructor.newInstance(target, source);
操作使用的就是MainActivity_ViewBinding
类两个参数的构造函数。
重点看这个构造函数,首先是给target.title
赋值,即MainActivity
中的TextView
,用到了一个findRequiredViewAsType()
方法:
public staticT findRequiredViewAsType(View source, @IdRes int id, String who, Class cls) { View view = findRequiredView(source, id, who); return castView(view, id, who, cls); }复制代码
继续看findRequiredView()
方法:
public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); if (view != null) { return view; } String name = getResourceEntryName(source, id); throw new IllegalStateException("Required view '" + name + "' with ID " + id + " for " + who + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'" + " (methods) annotation."); }复制代码
最终还是通过findViewById()
得到对应View,然后就是castView()
:
public staticT castView(View view, @IdRes int id, String who, Class cls) { try { return cls.cast(view); } catch (ClassCastException e) { String name = getResourceEntryName(view, id); throw new IllegalStateException("View '" + name + "' with ID " + id + " for " + who + " was of the wrong type. See cause for more info.", e); } }复制代码
核心就是把findRequiredView()
得到的 View 转成指定类型的 View ,如果 xml 中定义的 View 和 Activity 中通过注解绑定的 View 类型不一致,就会抛出上边方法的异常,可能很多人都遇到过。这样target.title
的赋值就结束了,接下来就是直接使用findRequiredView()
找到对应 id 的Button
,不用进行类型转换,然后给它绑定点击事件,最终调用了在MainActivity
中给Button
绑定点击事件时定义的submit()
方法。到这里就完成了 相关 View 的赋值以及事件绑定!
MainActivity_ViewBinding
类中还有一个unbind()
方法,需要在Activity
或Fragment
的onDestory()
中调用,以完成 相关 View 引用的释放以及点击事件的解绑操作!
三、注解处理器
那么,MainActivity_ViewBinding
类时如何生成的呢?首先,要生成这个类就要先得到这个类必须的基础信息,这就涉及到了annotationProcessor技术,和 APT(Annotation Processing Tool)技术类似,它是一种注解处理器,项目编译时对源代码进行扫描检测找出存活时间为RetentionPolicy.CLASS
的指定注解,然后对注解进行解析处理,进而得到要生成的类的必要信息,然后根据这些信息动态生成对应的 java 类,至于如何生成 java 类就涉及到了后边要说的 JavaPoet技术。
这里我们先看用注解处理器收集类信息的过程,之前我们已经在app的 build.gradle引入了 ButterKnife 的注解处理器: ,其中有一个 类完成了注解处理器的核心逻辑。这个类约有1500行代码,先看下它的基本框架结构:
@AutoService(Processor.class)public final class ButterKnifeProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); String sdk = env.getOptions().get(OPTION_SDK_INT); ...... debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE)); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); try { trees = Trees.instance(processingEnv); } catch (IllegalArgumentException ignored) { } } @Override public SetgetSupportedOptions() { return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE); } @Override public Set getSupportedAnnotationTypes() { Set types = new LinkedHashSet<>(); for (Class annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } @Override public boolean process(Set elements, RoundEnvironment env) { Map bindingMap = findAndParseTargets(env); for (Map.Entry 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; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }}复制代码
注意,ButterKnifeProcessor
类上使用了@AutoService(Processor.class)
注解,来实现注解处理器的注册,注册到 javac 后,在项目编译时就能执行注解处理器了。
ButterKnifeProcessor
继承了AbstractProcessor
抽象类,并重写以上五个方法,如果我们自定义解处理器也是类似的,看下这几个方法:
1、init()
首先 init() 方法完成sdk版本的判断以及相关帮助类的初始化,帮助类主要有以下几个:
- Elements elementUtils,注解处理器运行扫描源文件时,以获取元素(Element)相关的信息。Element 有以下几个子类: 包(
PackageElement
)、类(TypeElement
)、成员变量(VariableElement
)、方法(ExecutableElement
) - Types typeUtils,
- Filer filer,用来生成 java 类文件。
- Trees trees,
2、getSupportedAnnotationTypes()
该方法返回一个Set<String>
,代表ButterKnifeProcessor
要处理的注解类的名称集合,即 ButterKnife 支持的注解:
3、getSupportedSourceVersion()
返回当前系统支持的 java 版本。
4、getSupportedOptions()
返回注解处理器可处理的注解操作。
5、process()
最后,process() 方法是我们要重点分析的,在这里完成了目标类信息的收集并生成对应 java 类。
首先看注解信息的扫描收集,即通过findAndParseTargets()
方法:
private MapfindAndParseTargets(RoundEnvironment env) { Map builderMap = new LinkedHashMap<>(); Set erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); ...... ...... // env.getElementsAnnotatedWith(BindView.class)获取所有使用BindView注解的元素 for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } ...... ...... // 将builderMap中的数据添加到队列中 Deque > entries = new ArrayDeque<>(builderMap.entrySet()); Map bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { // 出队列 Map.Entry entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); // 查找当前类元素的父类元素 TypeElement parentType = findParentType(type, erasedTargetNames); // 如果没找到则保存TypeElement和对应BindingSet if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { // 如果找到父类元素,则给当前类元素对应的BindingSet.Builder设置父BindingSet builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // 再次入队列 entries.addLast(entry); } } } return bindingMap; }复制代码
只保留了BindView
注解信息的扫描解析过程,省略其它类似逻辑,先将扫描得到的注解相关信息保存到builderMap
和erasedTargetNames
中,最后对这些信息进行重新整理返回一个以TypeElement
为 key 、BindingSet
为 value 的 Map,其中TypeElement
代表使用了 ButterKnife 的类,即 Activity、Fragment等,BindingSet
是butterknife-compiler中的一个自定义类,用来存储要生成类的基本信息以及注解元素的相关信息。 parseBindView()
方法用来解析使用了BindView
注解的元素:
private void parseBindView(Element element, MapbuilderMap, Set erasedTargetNames) { // 首先要注意,此时element是VariableElement类型的,即成员变量 // enclosingElement是当前元素的父类元素,一般就是我们使用ButteKnife时定义的View类型成员变量所在的类,可以理解为之前例子中的MainActivity TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // 进行相关校验 // 1、isInaccessibleViaGeneratedCode(),先判断当前元素的是否是private或static类型, // 再判断其父元素是否是一个类以及是否是private类型。 // 2、isBindingInWrongPackage(),是否在系统相关的类中使用了ButteKnife注解 boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // TypeMirror表示Java编程语言中的一种类型。 类型包括基元类型,声明的类型(类和接口类型),数组类型,类型变量和空类型。 // 还表示了通配符类型参数,可执行文件的签名和返回类型,以及与包和关键字void相对应的伪类型。 TypeMirror elementType = element.asType(); // 如果当前元素是类的成员变量 if (elementType.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } Name qualifiedName = enclosingElement.getQualifiedName(); Name simpleName = element.getSimpleName(); // 判断当前元素是否是 View 的子类,或者是接口,不是的话抛出异常 if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) { if (elementType.getKind() == TypeKind.ERROR) { note(element, "@%s field with unresolved type (%s) " + "must elsewhere be generated as a View or interface. (%s.%s)", BindView.class.getSimpleName(), elementType, qualifiedName, simpleName); } else { error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName); hasError = true; } } if (hasError) { return; } // 获得元素使用BindView注解时设置的属性值,即 View 对应的xml中的id int id = element.getAnnotation(BindView.class).value(); // 尝试获取父元素对应的BindingSet.Builder BindingSet.Builder builder = builderMap.get(enclosingElement); // QualifiedId记录了当前元素的包信息以及id QualifiedId qualifiedId = elementToQualifiedId(element, id); if (builder != null) { String existingBindingName = builder.findExistingBindingName(getId(qualifiedId)); // 如果当前id已经被绑定,则抛出异常 if (existingBindingName != null) { error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBindingName, enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } else { // 创建一个新的BindingSet.Builder并返回,并且以enclosingElement 为key添加到builderMap中 builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); // 判断当前元素是否使用了Nullable注解 boolean required = isFieldRequired(element); // 创建一个FieldViewBinding,它包含了元素名、类型、是否是Nullable // 然后和元素id一同添加到BindingSet.Builder builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required)); // 记录当前元素的父类元素 erasedTargetNames.add(enclosingElement); }复制代码
部分代码已经写了注释,这里看下getOrCreateBindingBuilder()
方法的实现:
private BindingSet.Builder getOrCreateBindingBuilder( MapbuilderMap, TypeElement enclosingElement) { // 先判断enclosingElement对应的BindingSet.Builder是否已存在 BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder == null) { // 创建一个BindingSet.Builder builder = BindingSet.newBuilder(enclosingElement); // 添加到builderMap builderMap.put(enclosingElement, builder); } return builder; }复制代码
由于用BindingSet
保存了注解相关的信息,所以继续了解下它的newBuilder()
过程:
static Builder newBuilder(TypeElement enclosingElement) { TypeMirror typeMirror = enclosingElement.asType(); // 判断当前父元素的类型 boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE); boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE); boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE); TypeName targetType = TypeName.get(typeMirror); if (targetType instanceof ParameterizedTypeName) { targetType = ((ParameterizedTypeName) targetType).rawType; } // 获取父类元素的包名 String packageName = getPackage(enclosingElement).getQualifiedName().toString(); // 获取父类元素的名称 String className = enclosingElement.getQualifiedName().toString().substring( packageName.length() + 1).replace('.', '$'); // 即最终要生成的java类的名称 ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding"); // 判断父类元素是否为final类型 boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL); return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog); }复制代码
所以BindingSet
主要保存了要生成的目标类的基本特征信息,以及类中使用了 ButterKnife 注解的元素的信息,这样一个BindingSet
就和一个使用了ButterKnife 的类对应了起来。
四、JavaPoet
到这里要生成的目标类基本信息就收集就完成了,接下来就是生成 java 类文件了,再回到 process()方法:
@Overridepublic boolean process(Set elements, RoundEnvironment env) { MapbindingMap = findAndParseTargets(env); for (Map.Entry entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); // 得到java类源码 JavaFile javaFile = binding.brewJava(sdk, debuggable); try { // 生成java文件 javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false;}复制代码
遍历 bindingMap,根据BindingSet
得到一个JavaFile
对象,然后输入 java 类,这个过程用到了开源库,提供了一种友好的方式来辅助生成 java 类代码,同时将类代码生成文件,否则需要自己拼接字符串来实现,可以发现BindingSet
除了保存信息目标类信息外,还封装了 JavaPoet 生成目标类代码的过程。
在继续往下分析前,先了解下 JavaPoet 中一些重要的类(这些类还有许多实用的方法哦):
- TypeSpec 表示类、接口、或者枚举声明
- ParameterSpec 表示参数声明
- MethodSpec 表示构造函数、方法声明
- FieldSpec 表示成员变量,一个字段声明
- CodeBlock 表示代码块,用来拼接代码
- JavaFile 表示Java类的代码
还有几个占位符也了解下:
- **L; i < L i", "+")
- **S", "hello")
- **T()", Date.class)
- $N,for Names 替换JavaPoet中的声明
有了一些基础概念后,继续看用 JavaPoet 生成对应JavaFile
的过程:
JavaFile brewJava(int sdk, boolean debuggable) { return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable)) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }复制代码
用到的JavaFile.builder()
方法需要两个参数:要生成的目标类的包名,以及TypeSpec
对象,即createType()
方法的返回值,代表一个类、接口、枚举声明。结合文章开始的例子,这个TypeSpec
对象应代表一个类声明:
private TypeSpec createType(int sdk, boolean debuggable) { // TypeSpec.classBuilder() 方法设置类名称 // addModifiers() 方法设置类的修饰符为 public TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); // 如果是final类则添加final修饰符 if (isFinal) { result.addModifiers(FINAL); } if (parentBinding != null) { result.superclass(parentBinding.bindingClassName); } else { // 让当前类Unbinder接口,之前生成的MainActivity_ViewBinding类就实现了Unbinder接口 result.addSuperinterface(UNBINDER); } if (hasTargetField()) { // 添加一个类成员变量,可以对应到MainActivity_ViewBinding类中的private MainActivity target result.addField(targetTypeName, "target", PRIVATE); } // 判断目标类的类型 if (isView) { result.addMethod(createBindingConstructorForView()); } else if (isActivity) { // 由于之前的例子是Activity类型的,所以会走到这里,去生成MainActivity_ViewBinding类默认的构造函数 result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog()); } if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor()); } // 生成view绑定的构造函数,可以对应到MainActivity_ViewBinding类两个参数的构造函数 result.addMethod(createBindingConstructor(sdk, debuggable)); if (hasViewBindings() || parentBinding == null) { // 生成ubinder()方法 result.addMethod(createBindingUnbindMethod(result)); } return result.build(); }复制代码
这里重点看下createBindingConstructor()
的过程,代码较多,只保留部分以方便分析:
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC); if (hasMethodBindings()) { // 给构造函数添加一个修饰符为final、targetTypeName,名称为target的参数,即final MainActivity target constructor.addParameter(targetTypeName, "target", FINAL); } else { constructor.addParameter(targetTypeName, "target"); } if (constructorNeedsView()) { // 给构造函数添加一个View类型的source参数 constructor.addParameter(VIEW, "source"); } else { constructor.addParameter(CONTEXT, "context"); } ...... if (hasTargetField()) { // 给构造函数添加一行this.target = target;的声明代码 constructor.addStatement("this.target = target"); constructor.addCode("\n"); } if (hasViewBindings()) { if (hasViewLocal()) { // 给构造函数添加一行View view;的声明代码 constructor.addStatement("$T view", VIEW); } for (ViewBinding binding : viewBindings) { // 根据id查找view并完成赋值 addViewBinding(constructor, binding, debuggable); } ...... } ...... return constructor.build(); }复制代码
那么是如何根据id查找view呢?答案就在addViewBinding()
方法,以下解释同样类比MainActivity_ViewBinding两个参数的构造函数:
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { if (binding.isSingleFieldBinding()) { FieldViewBinding fieldBinding = binding.getFieldBinding(); // CodeBlock 表示代码块,用来完成view查找、赋值语句的拼接 // 相当于target.title = CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName()); // 由于例子中title是TextView,不是View所以requiresCast为true boolean requiresCast = requiresCast(fieldBinding.getType()); // debuggable为true、fieldBinding.isRequired()为true,则以下if条件不成立 if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) { if (requiresCast) { builder.add("($T) ", fieldBinding.getType()); } builder.add("source.findViewById($L)", binding.getId().code); } else { // 继续给代码块添加Utils.find builder.add("$T.find", UTILS); // 根据上边的分析可知,会给代码块添加RequiredView builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); if (requiresCast) { // 给代码块添加AsType builder.add("AsType"); } // 给代码块添加(source, R.id.tv_title builder.add("(source, $L", binding.getId().code); if (fieldBinding.isRequired() || requiresCast) { // 继续添加view的相关描述,例如field 'title' builder.add(", $S", asHumanDescription(singletonList(fieldBinding))); } if (requiresCast) { 继续添加view的Class类型,例如TextView.class builder.add(", $T.class", fieldBinding.getRawType()); } // 添加右括号,到这里就完成了view的查找与赋值 // 例如target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class); builder.add(")"); } result.addStatement("$L", builder.build()); return; }复制代码
结合对createType()
流程的分析,我们基本了解了 MainActivity_ViewBinding 类中构造函数的构建过程、以及 title(之前例子的TextView)的查找赋值的代码是如何构建出来的,这样就把注解处理器中 process()方法中BindView
注解的处理流程就跑通了。随然这只是一小部分的分析,但并不妨碍我们理解其它注解背后的工作流程。
五、小结
可以看出 ButterKnife 整个过程是在项目编译阶段完成的,主要用到了 annotationProcessor 和 JavaPoet 技术,使用时通过生成的辅助类完成操作,并不是在项目运行时通过注解加反射实现的,所以并不会影响项目运行时的性能,可能仅在项目编译时有略微的影响。