java黄油刀_ButterKnife原理解析看这篇文章就够了

原标题:ButterKnife原理解析看这篇文章就够了

作者:SheHuan

https://juejin.im/post/5acec2b46fb9a028c6761628

ButterKnife 算是一款知名老牌 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,很简单就不解释了:

publicclassMainActivityextendsAppCompatActivity{

@BindView(R.id.tv_title)

TextView title;

@OnClick(R.id.bt_submit)

publicvoidsubmit(){

title.setText( "hello world");

}

privateUnbinder unbinder;

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

unbinder = ButterKnife.bind( this);

}

@Override

protectedvoidonDestroy(){

unbinder.unbind();

super.onDestroy();

}

}

二、原理分析

最后编译一下项目。直觉告诉我们应该从ButterKnife.bind(this)开始分析,因为它像是 ButterKnife 和 Activity 建立绑定关系的过程,看具体的代码:

@NonNull@UiThread

publicstaticUnbinder bind(@NonNull Activity target){

View sourceView = target.getWindow().getDecorView();

returncreateBinding(target, sourceView);

}

sourceView代表当前界面的顶级父 View,是一个FrameLayout,继续看createBinding()方法:

privatestatic Unbinder createBinding( @NonNullObject target, @NonNullView source) {

Class> targetClass = target.getClass();

if(debug) Log.d(TAG, "Looking up binding for "+ targetClass.getName());

Constructor extends Unbinder> constructor= findBindingConstructorForClass(targetClass);

if( constructor== null) {

returnUnbinder.EMPTY;

}

//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.

try{

returnconstructor.newInstance(target, source);

}

// 省略了相关异常处理代码

}

首先得到要绑定的 Activity 对应的 Class,然后用根据 Class 得到一个继承了Unbinder的Constructor,最后通过反射constructor.newInstance(target, source)得到Unbinder子类的一个实例,到此ButterKnife.bind(this)操作结束。这里我们重点关注findBindingConstructorForClass()方法是如何得到constructor实例的:

@Nullable@CheckResult@UiThread

private staticConstructor extendsUnbinder> findBindingConstructorForClass(Class> cls) {

Constructor extendsUnbinder> bindingCtor = BINDINGS. get(cls);

if(bindingCtor != null) {

if(debug) Log.d(TAG, "HIT: Cached in binding map.");

returnbindingCtor;

}

StringclsName = cls.getName();

if(clsName.startsWith( "android.") || clsName.startsWith( "java.")) {

if(debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");

returnnull;

}

try{

Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

//noinspection unchecked

bindingCtor = (Constructor extendsUnbinder>) 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) {

thrownewRuntimeException( "Unable to find binding constructor for "+ clsName, e);

}

BINDINGS.put(cls, bindingCtor);

returnbindingCtor;

}

整体的流程是先检查BINDINGS是否存在 Class 对应的 Constructor,如果存在则直接返回,否则去构造对应的 Constructor。其中BINDINGS是一个LinkedHashMap:

Map, 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 extendsUnbinder>) bindingClass.getConstructor(cls, View.class);

BINDINGS.put(cls, bindingCtor);

所以最终bind()方法返回的是MainActivity_ViewBinding类的实例。既然可以返回MainActivity_ViewBinding的实例,那MainActivity_ViewBinding这个类肯定是存在的。可以在如下目录找到它(这个类是在项目编译时期由 annotationProcessor 生成的,关于 annotationProcessor 后边会说到):

来看看它里边都做了那些事:

publicclassMainActivity_ViewBindingimplementsUnbinder{

privateMainActivity target;

privateView view2131165217;

@UiThread

publicMainActivity_ViewBinding(MainActivity target){

this(target, target.getWindow().getDecorView());

}

@UiThread

publicMainActivity_ViewBinding(finalMainActivity 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( newDebouncingOnClickListener() {

@Override

publicvoiddoClick(View p0){

target.submit();

}

});

}

@Override

@CallSuper

publicvoidunbind(){

MainActivity target = this.target;

if(target == null) thrownewIllegalStateException( "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 static T findRequiredViewAsType(View source, @IdRes intid, String who,

Class cls) {

View view = findRequiredView(source, id, who);

returncastView(view, id, who, cls);

}

继续看findRequiredView()方法:

public staticView findRequiredView(View source, @IdResintid, Stringwho) {

View view = source.findViewById(id);

if(view != null) {

returnview;

}

Stringname = getResourceEntryName(source, id);

thrownewIllegalStateException( "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 static T castView(View view, @IdResintid, Stringwho, Class cls) {

try{

returncls.cast(view);

} catch(ClassCastException e) {

Stringname = getResourceEntryName(view, id);

thrownewIllegalStateException( "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 的注解处理器: butterknife-compiler,其中有一个ButterKnifeProcessor 类完成了注解处理器的核心逻辑。这个类约有1500行代码,先看下它的基本框架结构:

@AutoService(Processor. class)

public finalclassButterKnifeProcessorextendsAbstractProcessor{

@Override

public synchronized voidinit(ProcessingEnvironment env) {

super.init(env);

Stringsdk = 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 Set< String> getSupportedOptions() {

returnImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);

}

@Override

public Set< String> getSupportedAnnotationTypes() {

Set< String> types = newLinkedHashSet<>();

for(Class extendsAnnotation> annotation : getSupportedAnnotations()) {

types.add(annotation.getCanonicalName());

}

returntypes;

}

@Override

public boolean process( Set extendsTypeElement> 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());

}

}

returnfalse;

}

@Override

public SourceVersion getSupportedSourceVersion() {

returnSourceVersion.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 ,代表ButterKnifeProcessor要处理的注解类的名称集合,即 ButterKnife 支持的注解:butterknife-annotations

3、getSupportedSourceVersion()

返回当前系统支持的 java 版本。

4、getSupportedOptions()

返回注解处理器可处理的注解操作。

5、process()

最后,process() 方法是我们要重点分析的,在这里完成了目标类信息的收集并生成对应 java 类。

首先看注解信息的扫描收集,即通过findAndParseTargets()方法:

private Map findAndParseTargets(RoundEnvironment env) {

Map builderMap = newLinkedHashMap<>();

Set erasedTargetNames = newLinkedHashSet<>();

scanForRClasses(env);

......

......

// env.getElementsAnnotatedWith(BindView.class)获取所有使用BindView注解的元素

for( Elementelement : env.getElementsAnnotatedWith(BindView. class)) {

try{

parseBindView(element, builderMap, erasedTargetNames);

} catch(Exception e) {

logParsingError(element, BindView. class, e);

}

}

......

......

// 将builderMap中的数据添加到队列中

Deque< Map.Entry> entries =

newArrayDeque<>(builderMap.entrySet());

Map bindingMap = newLinkedHashMap<>();

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);

}

}

}

returnbindingMap;

}

只保留了BindView注解信息的扫描解析过程,省略其它类似逻辑,先将扫描得到的注解相关信息保存到builderMap和erasedTargetNames中,最后对这些信息进行重新整理返回一个以TypeElement为 key 、BindingSet为 value 的 Map,其中TypeElement代表使用了 ButterKnife 的类,即 Activity、Fragment等,BindingSet是butterknife-compiler中的一个自定义类,用来存储要生成类的基本信息以及注解元素的相关信息。

parseBindView()方法用来解析使用了BindView注解的元素:

private voidparseBindView( Elementelement, Map builderMap,

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

intid = element.getAnnotation(BindView. class).value();

// 尝试获取父元素对应的BindingSet.Builder

BindingSet.Builder builder = builderMap. get(enclosingElement);

// QualifiedId记录了当前元素的包信息以及id

QualifiedId qualifiedId = elementToQualifiedId(element, id);

if(builder != null) {

StringexistingBindingName = 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);

}

Stringname = simpleName.toString();

TypeName type = TypeName. get(elementType);

// 判断当前元素是否使用了Nullable注解

boolean required = isFieldRequired(element);

// 创建一个FieldViewBinding,它包含了元素名、类型、是否是Nullable

// 然后和元素id一同添加到BindingSet.Builder

builder.addField(getId(qualifiedId), newFieldViewBinding(name, type, required));

// 记录当前元素的父类元素

erasedTargetNames.add(enclosingElement);

}

部分代码已经写了注释,这里看下getOrCreateBindingBuilder()方法的实现:

privateBindingSet. Builder getOrCreateBindingBuilder(

Map builderMap, 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);

}

returnbuilder;

}

由于用BindingSet保存了注解相关的信息,所以继续了解下它的newBuilder()过程:

staticBuilder newBuilder(TypeElement enclosingElement){

TypeMirror typeMirror = enclosingElement.asType();

// 判断当前父元素的类型

booleanisView = isSubtypeOfType(typeMirror, VIEW_TYPE);

booleanisActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);

booleanisDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

TypeName targetType = TypeName.get(typeMirror);

if(targetType instanceofParameterizedTypeName) {

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类型

booleanisFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);

returnnewBuilder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);

}

所以BindingSet主要保存了要生成的目标类的基本特征信息,以及类中使用了 ButterKnife 注解的元素的信息,这样一个BindingSet就和一个使用了ButterKnife 的类对应了起来。

四、JavaPoet

到这里要生成的目标类基本信息就收集就完成了,接下来就是生成 java 类文件了,再回到 process()方法:

@Override

public boolean process( Set extendsTypeElement> elements, RoundEnvironment env) {

Map bindingMap = 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());

}

}

returnfalse;

}

遍历 bindingMap,根据BindingSet得到一个JavaFile对象,然后输入 java 类,这个过程用到了JavaPoet开源库,提供了一种友好的方式来辅助生成 java 类代码,同时将类代码生成文件,否则需要自己拼接字符串来实现,可以发现BindingSet除了保存信息目标类信息外,还封装了 JavaPoet 生成目标类代码的过程。

在继续往下分析前,先了解下 JavaPoet 中一些重要的类(这些类还有许多实用的方法哦):

TypeSpec表示类、接口、或者枚举声明

ParameterSpec表示参数声明

MethodSpec表示构造函数、方法声明

FieldSpec表示成员变量,一个字段声明

CodeBlock表示代码块,用来拼接代码

JavaFile表示Java类的代码

有了一些基础概念后,继续看用 JavaPoet 生成对应JavaFile的过程:

JavaFile brewJava(intsdk, booleandebuggable){

returnJavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))

.addFileComment( "Generated code from Butter Knife. Do not modify!")

.build();

}

用到的JavaFile.builder()方法需要两个参数:要生成的目标类的包名,以及TypeSpec对象,即createType()方法的返回值,代表一个类、接口、枚举声明。结合文章开始的例子,这个TypeSpec对象应代表一个类声明:

privateTypeSpec createType(intsdk, booleandebuggable){

// 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());

} elseif(isActivity) {

// 由于之前的例子是Activity类型的,所以会走到这里,去生成MainActivity_ViewBinding类默认的构造函数

result.addMethod(createBindingConstructorForActivity());

} elseif(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));

}

returnresult.build();

}

这里重点看下createBindingConstructor()的过程,代码较多,只保留部分以方便分析:

privateMethodSpec 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( "$Tview", VIEW);

}

for(ViewBinding binding : viewBindings) {

// 根据id查找view并完成赋值

addViewBinding( constructor, binding, debuggable);

}

......

}

......

returnconstructor.build();

}

那么是如何根据id查找view呢?答案就在addViewBinding()方法,以下解释同样类比MainActivity_ViewBinding两个参数的构造函数:

privatevoidaddViewBinding(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", asHumanDeion(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 技术,使用时通过生成的辅助类完成操作,并不是在项目运行时通过注解加反射实现的,所以并不会影响项目运行时的性能,可能仅在项目编译时有略微的影响。返回搜狐,查看更多

责任编辑:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值