使用
-
设置依赖及插件
- 在根gradle中,配置
ButterKnife
插件
buildscript { dependencies { ... classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0' } }
- 在app gradle中,引用
ButterKnife
插件及设置ButterKnife
依赖
// 引用`ButterKnife`插件 apply plugin: 'com.jakewharton.butterknife' dependencies { // 设置`ButterKnife`依赖 implementation 'com.jakewharton:butterknife:10.1.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' }
- 在根gradle中,配置
-
在项目中使用
public class MainActivity extends AppCompatActivity {
// 添加注解
@BindView(R.id.hello_text)
TextView helloText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//
ButterKnife.bind(this);
}
@OnClick(R.id.hello_text)
public void onViewClicked() {
Toast.makeText(this, helloText.getText(), Toast.LENGTH_SHORT).show();
}
}
- 运行
gradle build
或gradle rebuild
命令,在app/build/generated/source/apt/debug
目录相应的包下会找到生成的类MainActivity_ViewBinding
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f070040;
@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.hello_text, "field 'helloText' and method 'onViewClicked'");
target.helloText = Utils.castView(view, R.id.hello_text, "field 'helloText'", TextView.class);
view7f070040 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onViewClicked();
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.helloText = null;
view7f070040.setOnClickListener(null);
view7f070040 = null;
}
}
ButterKnife的执行流程大体如下:
- 在编译时,通过注解处理器(
AnnatitionProcessors
)扫描并处理声明的注解,生成相应的Java文件。- 定义扫注解
- 定义
AbstractProcessor
的实现类并重写相应的方法 - 注册注解处理器
- 在Activity、Fragment或者其他,通过调用
ButterKnife.bind(this)
方法,查找相应的生成的xxx_ViewBinding
并执行,完成findViewBingding
、setOnClick
等操作。
源码解析
1. 定义注解
在butter-knife-10.1.0
jar包中,声明了一系列用于注解处理器注册处理的注解,比如@BindView
、@BindViews
、@onClick
等等。以@BindView
为例,其声明如下:
@Retention(RUNTIME)
@Target(FIELD)
public @interface BindView {
@IdRes int value();
}
其中:
@Retention(RUNTIME)
:表明注解只保留在源文件,当.java文件
编译成.class
文件时,注解被遗弃。@Target(FIELD)
:表明注解只能用于注解字段@IdRes int value()
:表明注解的属性为int
类型,而且其值为资源引用id,比如android.R.id.hello_text
。
2. 定义AbstractProcessor
的实现类
在自定义注解的处理工具一般都是继承自AbstractProcessor
。
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
***
// 用来处理TypeMirror的工具类.
private Types typeUtils;
// 创建文件的工具类
private Filer filer;
/**
* 初始化方法
* ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。
*/
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
***
}
/**
* 处理器识别的选项
*/
@Override public Set<String> getSupportedOptions() {
return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
}
/**
* 用来指定使用的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 注解处理器是注册给哪个注解的
*/
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
//返回支持注解的类型
return types;
}
/**
* 扫描、评估和处理注解的代码,以及生成Java文件。
*/
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 获取所有注册的注解信息,TypeElement 作为 key,BindingSet 作为 value
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
// 遍历 map 里面的所有信息,并生成 java 代码
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 生成 javaFile 对象
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;
}
***
}
init(ProcessingEnvironment env)
:注解处理器的初始化方法,它会被注解处理工具调用,并传入ProcessingEnviroment
参数,而ProcessingEnviroment
会提供很多有用的工具类,比如Elements
,Types
和Filer
等等。getSupportedSourceVersion()
:用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()
。getSupportedAnnotationTypes()
:用来声明注解处理器注册处理哪些注解。其返回值是一个字符串的集合,字符串元素为本处理器想要处理的注解类型的合法全称。process(Set<? extends TypeElement> annotations, RoundEnvironment env)
: 注解处理器的核心方法,主要负责扫描、评估和处理注解的代码,以及生成Java文件。传入的参数RoundEnviroment
可以让查询出包含特定注解的被注解元素。
不管是init(ProcessingEnvironment env)
还是getSupportedSourceVersion()
,都是一些常规的处理模式。在ButterKnifeProcessor
中,值得关注的是注册处理哪些注解和怎么处理注解并生成Java文件的。
在ButterKnifeProcessor
中,重写getSupportedSourceVersion()
方法,注册了一系列支持处理的注解,详见getSupportedAnnotations()
法。
/**
* 注解处理器是注册给哪个注解的
*/
@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(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;
}
接下来就是核心部分 - process()
方法,它主要完成了以下工作:
- 扫描所有注册的注解信息,处理并这些注解注解的元素的信息,将这些信息整合成
BindingSet
,它实际就是包含构建Java文件的所有元素信息的对象,然后将这些BindingSet
缓存到map
中 - 遍历
map
里面的BindingSet
,生成相应的Java文件。
/**
* 扫描、评估和处理注解的代码,以及生成Java文件。
*/
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 获取所有注册的注解信息,TypeElement 作为 key,BindingSet 作为 value
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
// 遍历 map 里面的所有信息,并生成 java 代码
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 生成 javaFile 对象
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;
}
扫描&处理注解
在process()
中,通过调用findAndParseTargets()
方法来处理被注解的元素信息。在findAndParseTargets()
方法中,主要做了两件事:
- 通过注解处理工具传入的
RoundEnvironment
对象调用getElementsAnnotatedWith()
获取所有被某个注解注解的元素列表,将解析成的BindingSet.Builder
的放入map中缓存; - 将超类Binder与其子类Binder关联。
先看findAndParseTargets()
方法的前半部分,了解是如何将注解解析成BindingSet
对象的。
/**
* 获取所有注册的注解信息,TypeElement(被注解元素所在的top_level类) 作为 key,BindingSet 作为 value
*/
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
// 用于保存所有被注解元素所在的top_level类,为超类Binder与其子类Binder关联做准备
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// 处理 @BindAnim 注解
for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceAnimation(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindAnim.class, e);
}
}
// 处理 @BindArray 注解
for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceArray(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindArray.class, e);
}
}
// 处理 @BindBitmap 注解
for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceBitmap(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindBitmap.class, e);
}
}
// 处理 @BindBool 注解
for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceBool(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindBool.class, e);
}
}
// 处理 @BindColor 注解
for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceColor(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindColor.class, e);
}
}
// 处理 @BindDimen 注解
for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceDimen(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindDimen.class, e);
}
}
// 处理 @BindDrawable 注解
for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceDrawable(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindDrawable.class, e);
}
}
// 处理 @BindFloat 注解
for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceFloat(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindFloat.class, e);
}
}
// 处理 @BindFont 注解
for (Element element : env.getElementsAnnotatedWith(BindFont.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceFont(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindFont.class, e);
}
}
// 处理 @BindInt 注解
for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceInt(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindInt.class, e);
}
}
// 处理 @BindString 注解
for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceString(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindString.class, e);
}
}
// 处理 @BindView 注解
// 遍历所有被 @BindView注解的元素并处理
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
// 处理 @BindViews 注解
for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindViews(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindViews.class, e);
}
}
// 处理与监听对应的每个注解.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
***
}
对于不同的注解,findAndParseTargets()
采用了类似的处理方式,目的都是一样的,就是遍历所有被注解的元素,并创建相应BindingSet.Builder
实例,再添加到缓存。以@BindView
为例,对于单个被注解的元素的处理是调用parseBindView(element, builderMap, erasedTargetNames)
方法,传递3个参数:
element
:待处理的元素builderMap
:缓存BindingSet.Builder
的MaperasedTargetNames
:缓存所有被注解的元素所属的TypeElement。该Set在处理超类Binder与其子类Binder关联时使用。
在parseBindView()
中,做了如下操作:
- 获取元素所属的TypeElement,以下称
enclosingElement
- 判断被注解
@BindView
修饰的成员变量的合法性- 如果该属性是被 private 或者 static 修饰的,则出错
- 如果该属性所在top_lel Class不是 Class类型,比如接口或者枚举类,则出错
- 如果该属性所在top_lel Class是被 private 修饰的,则出错
- 判断是否被注解在错误的包中,若包名以“android”或者“java”开头,则出错
- 判断元素的类型是否为View的子类,如果不是,则报错。
- 判断元素是不是View及其子类或者是不是Interface
- 获取View的id,即
@BindView
的属性值 - 创建或设置
BindingSet.Builder
:- 验证缓存map中key为
enclosingElement
的BindingSet.Builder
是否存在,如果存在,使用缓存中的BindingSet.Builder
。- 需要验证当前id下是否已经绑定,如果已绑定,则报错。
- 如果不存在,那么需要新创建
BindingSet.Builder
,并添加到缓存
- 验证缓存map中key为
- 获取被注解修饰的字段的信息:字段的名称、类型以及是否需要初始化
- 将字段绑定信息(
FieldViewBinding
)添加到BindingSet.Builder
- 将元素所属的TypeElement添加到缓存
erasedTargetNames
/**
* 处理 @BindView 并将元素信息添加至对应的BindingSet.Builder
*/
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
// 获取注解元素所在的元素
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 1. 判断被注解 @BindView 修饰的成员变量是不是合法的
// 1.1 如果该属性是被 private 或者 static 修饰的,则出错
// 1.2 如果该属性所在top_lel Class不是 Class类型,比如接口或者枚举类,则出错
// 1.3 如果该属性所在top_lel Class是被 private 修饰的,则出错
// 2. 判断是否被注解在错误的包中,若包名以“android”或者“java”开头,则出错
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// 判断元素的类型是否为View的子类,如果不是,则报错。
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及其子类或者是不是Interface
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;
}
// 获取注解的value值,即为 View的id(R.id.Xxxx)
int id = element.getAnnotation(BindView.class).value();
// 根据所在的类元素去查找 builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
// 如果相应的 builder 已经存在
if (builder != null) {
// 验证 ID 是否已经被绑定
// 如果未绑定,返回null
// 如果已绑定,返回绑定的字段名称
String existingBindingName = builder.findExistingBindingName(resourceId);
// 被绑定了,出错,返回
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 {
// 如果没有相应的 builder,就需要新生成,并别存放到 builderMap 中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
// 字段的名称
String name = simpleName.toString();
// 字段的类型
TypeName type = TypeName.get(elementType);
// 判断该字段是否一定被初始化
boolean required = isFieldRequired(element);
// 将该字段添加到BindingSet.Builder
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
现在来看,findAndParseTargets()
方法的后半部分,它做的唯一一件事就是遍历前面部分用来缓存BindingSet.Builder
的Map,有两个目的:
- 超类Binder与其子类Binder关联
- 创建
BindingSet
并添加至缓存Map<TypeElement, BindingSet>
/**
* 获取所有注册的注解信息,TypeElement(被注解元素所在的top_level类) 作为 key,BindingSet 作为 value
*/
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
***
// 将超类Binder与其子类Binder关联。 这是一个基于队列的树遍历,它从根(超类)开始,然后遍历叶子(子类)。
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();
// 获取 type 的父类的 TypeElement
TypeElement parentType = findParentType(type, erasedTargetNames);
// 为空,存进 map
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
// 获取 parentType 的 BindingSet
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// 如果父类Binder的BindingSet还未创建,将其加到队列的尾部,等待下一次处理
entries.addLast(entry);
}
}
}
return bindingMap;
}
生成Java
模板代码
从前面的分析了解到,通过调用findAndParseTargets()
方法,解析注册的注解信息
并将所有需要创建Java文件的TypeElement添加到缓存 - Map<TypeElement, BindingSet> bindingMap
。此时,只需遍历bindingMap
,生成Java
模板代码的核心代码如下:
// 遍历 map 里面的所有信息,并生成 java 代码
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 生成 javaFile 对象
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());
}
}
BindingSet
实例调用brewJava()
方法生成Java
模板代码。在brewJava()
中,依据BindingSet
,调用JavaPoet
一系列方法用来:
- 创建
Java
模板文件,添加修饰符及继承关系 - 创建构造函数
/**
* 生成Java文件
*/
JavaFile brewJava(int sdk, boolean debuggable) {
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
// 创建构造函数
// 如果是 View 或者是 View 的子类的话,添加构造方法
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) { // 如果是 Activity 或者是 Activity 的子类的话,添加构造方法
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) { // 如果是 Dialog 或者是 Dialog 的子类的话,添加构造方法
result.addMethod(createBindingConstructorForDialog());
}
// 如果构造方法不需要 View 参数,添加 需要 View 参数的构造方法
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
/**
* 创建绑定构造函数
*/
result.addMethod(createBindingConstructor(sdk, debuggable));
if (hasViewBindings() || parentBinding == null) {
//生成unBind方法
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
接下来,看创建绑定构造函数createBindingConstructor(sdk, debuggable)
:
- 根据是否有ViewBinding和方法绑定设置构造函数的参数
- 判断是否有继承关系,如果有,添加调用父类的构造方法
- 遍历
viewBindings
,调用addViewBinding
生成source.findViewById($L)
代码 - 遍历
resourceBindings
,通过调用ResourceBinding.render()
方法生成根据id获取资源的代码块
/**
* 创建绑定构造函数
*/
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
// 如果有方法绑定,比如 @onClick,那么增加一个 targetTypeName 类型 的方法参数 target,并且是 final 类型的
if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);
} else { // 如果没有 ,不是 final 类型的
constructor.addParameter(targetTypeName, "target");
}
//如果有注解的 View,那么添加 VIEW 类型 source 参数
if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");
} else {
// 添加 Context 类型的 context 参数
constructor.addParameter(CONTEXT, "context");
}
if (hasUnqualifiedResourceBindings()) {
// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "ResourceType")
.build());
}
// 如果 @OnTouch 绑定 View,添加 @SuppressLint("ClickableViewAccessibility")
if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}
// 如果 parentBinding 不为空,调用父类 的构造方法
if (parentBinding != null) {
if (parentBinding.constructorNeedsView()) {
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {
constructor.addStatement("super(target, source.getContext())");
} else {
constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}
// 添加成员变量
if (hasTargetField()) {
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
// 遍历 viewBindings,生成 source.findViewById($L) 代码
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding, debuggable);
}
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render(debuggable));
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}
if (!resourceBindings.isEmpty()) {
if (constructorNeedsView()) {
constructor.addStatement("$T context = source.getContext()", CONTEXT);
}
if (hasResourceBindingsNeedingResource(sdk)) {
constructor.addStatement("$T res = context.getResources()", RESOURCES);
}
for (ResourceBinding binding : resourceBindings) {
constructor.addStatement("$L", binding.render(sdk));
}
}
return constructor.build();
}
在addViewBinding
中,生成source.findViewById($L)
以对绑定的View初始化。由于方法内是用target.
的方式引用被注解的字段,因而被注解的字段不能被private
修饰。
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
// Optimize the common case where there's a single binding directly to a field.
FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
// 生成`findView`代码块
// 由于这里是用的`target.`,也就是调用了注解的字段,因而被注解的字段不能被`private`修饰
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
builder.add("($T) ", fieldBinding.getType());
}
builder.add("source.findViewById($L)", binding.getId().code);
} else {
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
3. 绑定注入
在通过编译后,注解处理器(AnnatitionProcessors
)扫描并处理声明的注解,生成相应的Java文件,即xxx_ViewBindg.Java
。此时,相应的findView
方法已经生成且是构造函数的代码块,是如何注入的呢?ButterKnife.bind()
是所熟知的方式。下面看下bind()
实现方式:
@NonNull @UiThread
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) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}
在bind()
方法里通过调用findBindingConstructorForClass()
查找所对应的xxx_ViewBinding
的构造函数对象。然后,利用反射来实例化xxx_ViewBinding
,以达到注入的目的。
在findBindingConstructorForClass()
查找对象时,主体思路是这样的:
- 读取缓存,若缓存中存在,直接返回。
- 判断传入的
Class
是否合法,如果不合法,直接返回null - 利用类加载器加载生成的
class
文件,并获取其构造方法。如果可以获取到,直接返回。如果获取不到,会抛出异常。在异常的处理中,再查找当前class
文件的父类的_ViewBinding
。在返回之前,将结果存进map
集合中,做缓存处理。
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
// 读取缓存,如果不为空,直接返回
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
// 如果是 android ,java 原生的文件,不处理
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<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
// 在原来所在的类查找
bindingCtor = (Constructor<? extends Unbinder>) 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);
}
// 存进 LinkedHashMap 缓存
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
}
到这里,ButterKnife的源码分析结束。其核心就是通过注解处理器生成Java模板,在运行时直接调用,避免了无谓的重复工作,提高开发效率。