深入理解Android Butterknife注解处理原理
一、Java注解机制概述
1.1 注解的基本概念
Java注解(Annotation)是Java 5.0引入的一种元数据机制,它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。与普通注释不同的是,注解可以在编译时、运行时被读取,并执行相应的处理逻辑。
以下是一个简单的注解定义示例:
// 定义一个简单的注解
public @interface MyAnnotation {
String value() default ""; // 定义一个名为value的成员,默认值为空字符串
int id() default 0; // 定义一个名为id的成员,默认值为0
}
1.2 注解的分类
Java注解可以分为以下几类:
1.2.1 标准注解
Java内置了一些标准注解,例如:
@Override:用于标记一个方法覆盖了父类的方法@Deprecated:用于标记一个方法或类已经过时@SuppressWarnings:用于抑制编译器警告
1.2.2 元注解
元注解是用于定义注解的注解,Java提供了4个标准元注解:
@Retention:指定注解的保留策略@Target:指定注解可以应用的程序元素类型@Documented:指定注解应该被包含在JavaDoc中@Inherited:指定注解可以被继承
以下是一个使用元注解定义注解的示例:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定注解的保留策略为RUNTIME,即在运行时可以通过反射访问
@Retention(RetentionPolicy.RUNTIME)
// 指定注解可以应用于类、接口、方法和字段
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface MyAnnotation {
String value() default "";
int id() default 0;
}
1.2.3 自定义注解
开发者可以根据自己的需求定义注解,如上面的MyAnnotation就是一个自定义注解。
1.3 注解的保留策略
注解的保留策略由@Retention元注解指定,Java提供了三种保留策略:
1.3.1 RetentionPolicy.SOURCE
注解只在源代码中保留,编译时会被编译器丢弃,不会包含在class文件中。例如:@Override、@SuppressWarnings等注解。
1.3.2 RetentionPolicy.CLASS
注解会被保留在class文件中,但在运行时不会被虚拟机保留。这是默认的保留策略。
1.3.3 RetentionPolicy.RUNTIME
注解会被保留在class文件中,并且在运行时可以通过反射机制访问。例如:@Deprecated注解。
以下是一个指定保留策略的注解示例:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// 指定注解的保留策略为RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRuntimeAnnotation {
String value() default "";
}
1.4 注解的应用场景
注解的应用场景非常广泛,常见的应用场景包括:
1.4.1 编译时检查
例如,@Override注解用于在编译时检查方法是否正确覆盖了父类的方法。
1.4.2 代码生成
通过注解处理器在编译时生成额外的代码,例如Butterknife就是利用注解处理器在编译时生成视图绑定代码。
1.4.3 运行时反射
通过反射机制在运行时获取注解信息,并执行相应的处理逻辑,例如JUnit框架就是利用注解来标识测试方法。
1.4.4 配置文件替代
通过注解来替代繁琐的XML或properties配置文件,例如Spring框架中的@Component、@Autowired等注解。
二、Butterknife注解处理器基础
2.1 注解处理器概述
注解处理器(Annotation Processor)是Java编译器的一个工具,它可以在编译时扫描和处理注解,并生成额外的Java代码或资源文件。注解处理器的主要作用是在编译阶段对注解进行处理,从而减少运行时的开销。
Butterknife就是利用注解处理器在编译时生成视图绑定代码,避免了在运行时使用反射,从而提高了性能。
2.2 Butterknife注解处理器的作用
Butterknife注解处理器的主要作用是在编译时扫描Activity、Fragment、ViewHolder等类中的Butterknife注解(如@BindView、@OnClick等),并生成相应的视图绑定代码。这些生成的代码会在运行时被调用,从而实现视图的绑定和事件的处理。
Butterknife注解处理器的工作流程如下:
- 编译器在编译Java源文件时,会发现Butterknife注解
- 注解处理器会扫描这些注解,并生成对应的绑定类
- 生成的绑定类会被编译成class文件,并打包到APK中
- 在运行时,Butterknife会通过反射或直接调用生成的绑定类来完成视图绑定和事件处理
2.3 Butterknife注解处理器的依赖配置
要使用Butterknife注解处理器,需要在项目的build.gradle文件中添加相应的依赖:
// 添加Butterknife运行时库依赖
implementation 'com.jakewharton:butterknife:10.2.3'
// 添加Butterknife注解处理器依赖
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
如果你使用的是Kotlin项目,还需要添加KAPT插件:
// 应用KAPT插件
apply plugin: 'kotlin-kapt'
dependencies {
// 添加Butterknife运行时库依赖
implementation 'com.jakewharton:butterknife:10.2.3'
// 使用kapt替代annotationProcessor
kapt 'com.jakewharton:butterknife-compiler:10.2.3'
}
2.4 Butterknife核心注解介绍
Butterknife提供了一系列注解,用于简化Android开发中的视图绑定和事件处理。以下是Butterknife的几个核心注解:
2.4.1 @BindView
@BindView注解用于绑定单个视图,它接收一个视图ID作为参数:
import butterknife.BindView;
import butterknife.ButterKnife;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
// 使用@BindView注解绑定R.id.text_view资源ID对应的TextView
@BindView(R.id.text_view)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 调用ButterKnife的bind方法完成视图绑定
ButterKnife.bind(this);
// 可以直接使用textView,无需再调用findViewById
textView.setText("Hello Butterknife!");
}
}
2.4.2 @BindViews
@BindViews注解用于绑定多个视图到一个列表中:
import butterknife.BindViews;
import butterknife.ButterKnife;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
public class MainActivity extends AppCompatActivity {
// 使用@BindViews注解绑定多个TextView到一个列表中
@BindViews({R.id.text_view1, R.id.text_view2, R.id.text_view3})
List<TextView> textViews;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
// 可以直接操作textViews列表
for (TextView textView : textViews) {
textView.setText("Hello Butterknife!");
}
}
}
2.4.3 @OnClick
@OnClick注解用于简化点击事件的处理:
import butterknife.BindView;
import butterknife.OnClick;
import butterknife.ButterKnife;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text_view)
TextView textView;
@BindView(R.id.button)
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
// 使用@OnClick注解处理button的点击事件
@OnClick(R.id.button)
public void onButtonClick() {
textView.setText("Button clicked!");
}
}
2.4.4 @OnLongClick
@OnLongClick注解用于处理长按事件:
import butterknife.BindView;
import butterknife.OnLongClick;
import butterknife.ButterKnife;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text_view)
TextView textView;
@BindView(R.id.button)
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
// 使用@OnLongClick注解处理button的长按事件
@OnLongClick(R.id.button)
public boolean onButtonLongClick() {
textView.setText("Button long clicked!");
return true; // 返回true表示事件已处理
}
}
2.4.5 @OnTextChanged
@OnTextChanged注解用于监听文本变化事件:
import butterknife.BindView;
import butterknife.OnTextChanged;
import butterknife.ButterKnife;
import android.os.Bundle;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.edit_text)
EditText editText;
@BindView(R.id.text_view)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
// 使用@OnTextChanged注解监听editText的文本变化事件
@OnTextChanged(R.id.edit_text)
public void onTextChanged(CharSequence s, int start, int before, int count) {
textView.setText("Current text: " + s);
}
}
三、自定义注解处理器开发
3.1 注解处理器的基本结构
要开发一个自定义注解处理器,需要继承AbstractProcessor类,并实现其中的一些方法。下面是一个简单的注解处理器示例:
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;
// 指定该处理器支持的注解类型
@SupportedAnnotationTypes("com.example.MyAnnotation")
// 指定支持的Java版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 处理注解的逻辑
return true; // 返回true表示这些注解已经被当前处理器处理,不需要其他处理器再处理
}
}
3.2 注解处理器的注册
为了让编译器知道我们的注解处理器,需要在META-INF/services目录下创建一个名为javax.annotation.processing.Processor的文件,文件内容为我们的注解处理器的全限定名。
例如,对于上面的MyAnnotationProcessor,需要创建文件META-INF/services/javax.annotation.processing.Processor,内容为:
com.example.MyAnnotationProcessor
在Gradle项目中,可以使用以下配置自动生成这个文件:
dependencies {
// 添加注解处理器依赖
implementation 'com.google.auto.service:auto-service:1.0-rc7'
}
// 使用auto-service注解自动生成META-INF/services/javax.annotation.processing.Processor文件
@AutoService(Processor.class)
public class MyAnnotationProcessor extends AbstractProcessor {
// 处理器实现
}
3.3 开发一个简单的注解处理器
下面我们来开发一个简单的注解处理器,用于处理自定义的@BindView注解。
首先,定义我们的注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定注解的保留策略为CLASS,即在编译时处理
@Retention(RetentionPolicy.CLASS)
// 指定注解可以应用于字段
@Target(ElementType.FIELD)
public @interface SimpleBindView {
int value(); // 视图ID
}
然后,创建我们的注解处理器:
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.JavaFileObject;
// 使用AutoService自动注册处理器
@AutoService(Processor.class)
// 指定支持的注解类型
@SupportedAnnotationTypes("com.example.SimpleBindView")
// 指定支持的Java版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SimpleBindViewProcessor extends AbstractProcessor {
// 文件生成器,用于生成Java文件
private Filer filer;
// 元素工具类,用于处理元素相关的操作
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
elementUtils = processingEnv.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 存储每个类及其对应的字段信息
Map<String, ClassInfo> classMap = new HashMap<>();
// 遍历所有被@SimpleBindView注解的元素
for (Element element : roundEnv.getElementsAnnotatedWith(SimpleBindView.class)) {
// 检查元素类型是否为字段
if (element.getKind() != ElementKind.FIELD) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"@SimpleBindView注解只能用于字段", element);
continue;
}
// 将元素转换为字段元素
VariableElement fieldElement = (VariableElement) element;
// 获取字段所在的类元素
TypeElement classElement = (TypeElement) fieldElement.getEnclosingElement();
// 获取类的全限定名
String className = classElement.getQualifiedName().toString();
// 获取或创建类信息
ClassInfo classInfo = classMap.computeIfAbsent(className, k -> new ClassInfo(className));
// 获取注解实例
SimpleBindView annotation = fieldElement.getAnnotation(SimpleBindView.class);
// 获取视图ID
int viewId = annotation.value();
// 获取字段类型
TypeMirror fieldType = fieldElement.asType();
// 获取字段名称
String fieldName = fieldElement.getSimpleName().toString();
// 添加字段信息到类信息中
classInfo.addField(viewId, fieldType.toString(), fieldName);
}
// 生成绑定类
for (ClassInfo classInfo : classMap.values()) {
try {
generateBindingClass(classInfo);
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"生成绑定类失败: " + e.getMessage());
}
}
return true;
}
// 生成绑定类的方法
private void generateBindingClass(ClassInfo classInfo) throws IOException {
// 获取包名
String packageName = elementUtils.getPackageOf(classInfo.getClassName()).getQualifiedName().toString();
// 获取类名(不包含包名)
String className = classInfo.getClassName().substring(packageName.length() + 1);
// 生成的绑定类名
String bindingClassName = className + "_ViewBinding";
// 创建Java文件
JavaFileObject jfo = filer.createSourceFile(packageName + "." + bindingClassName);
Writer writer = jfo.openWriter();
try {
// 写入包声明
writer.write("package " + packageName + ";\n\n");
// 写入导入语句
writer.write("import android.view.View;\n");
writer.write("import com.example.BindHelper;\n\n");
// 写入类声明
writer.write("public class " + bindingClassName + " {\n");
// 写入构造函数
writer.write(" public " + bindingClassName + "(final Object target, View source) {\n");
// 写入字段绑定代码
for (FieldInfo fieldInfo : classInfo.getFields()) {
writer.write(" " + fieldInfo.getType() + " " + fieldInfo.getName() + " = " +
"((" + fieldInfo.getType() + ") source.findViewById(" + fieldInfo.getId() + "));\n");
writer.write(" BindHelper.bindField(target, \"" + fieldInfo.getName() + "\", " +
fieldInfo.getName() + ");\n");
}
writer.write(" }\n");
writer.write("}\n");
} finally {
writer.close();
}
}
// 类信息类,用于存储类及其字段信息
private static class ClassInfo {
private String className;
private List<FieldInfo> fields = new ArrayList<>();
public ClassInfo(String className) {
this.className = className;
}
public String getClassName() {
return className;
}
public List<FieldInfo> getFields() {
return fields;
}
public void addField(int id, String type, String name) {
fields.add(new FieldInfo(id, type, name));
}
}
// 字段信息类,用于存储字段的ID、类型和名称
private static class FieldInfo {
private int id;
private String type;
private String name;
public FieldInfo(int id, String type, String name) {
this.id = id;
this.type = type;
this.name = name;
}
public int getId() {
return id;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
}
}
3.4 注解处理器的工作流程
注解处理器的工作流程主要分为以下几个步骤:
-
初始化:编译器在启动时会初始化所有注册的注解处理器,并调用其
init方法。 -
处理注解:编译器在编译Java源文件时,会将发现的注解传递给注解处理器的
process方法进行处理。 -
扫描注解元素:在
process方法中,注解处理器会扫描所有被指定注解标注的元素,并提取相关信息。 -
生成代码:根据提取的信息,注解处理器可以生成额外的Java代码或资源文件。
-
返回结果:处理完成后,注解处理器返回一个布尔值,表示这些注解是否已经被完全处理。
3.5 注解处理器的调试技巧
开发注解处理器时,调试是一个挑战,因为注解处理器是在编译时运行的。以下是一些调试技巧:
-
使用日志输出:在注解处理器中使用
processingEnv.getMessager().printMessage()方法输出调试信息。 -
使用断点调试:可以通过以下步骤在注解处理器中设置断点进行调试:
- 在IDE中创建一个远程调试配置
- 使用以下命令编译项目并启用调试:
gradle clean build --no-daemon -Dorg.gradle.debug=true - 连接远程调试器到运行的Gradle进程
-
生成中间文件:在注解处理器中生成中间文件,以便查看处理过程中的数据和生成的代码。
四、Butterknife注解处理器源码分析
4.1 Butterknife注解处理器的入口类
Butterknife注解处理器的入口类是ButterKnifeProcessor,它继承自AbstractProcessor类,并处理Butterknife的各种注解。
下面是ButterKnifeProcessor类的部分源码:
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({
"butterknife.BindView",
"butterknife.BindViews",
"butterknife.BindArray",
"butterknife.BindBool",
"butterknife.BindColor",
"butterknife.BindDimen",
"butterknife.BindDrawable",
"butterknife.BindInt",
"butterknife.BindString",
"butterknife.OnCheckedChanged",
"butterknife.OnClick",
"butterknife.OnEditorAction",
"butterknife.OnFocusChange",
"butterknife.OnItemClick",
"butterknife.OnItemLongClick",
"butterknife.OnItemSelected",
"butterknife.OnLongClick",
"butterknife.OnPageChange",
"butterknife.OnTextChanged",
"butterknife.OnTouch"
})
public final class ButterKnifeProcessor extends AbstractProcessor {
// 用于存储每个类的绑定信息
private final Map<TypeElement, BindingSet> targetClassMap = new LinkedHashMap<>();
// 用于存储资源ID到名称的映射
private final Map<Integer, String> R_CLASS_MEMBERS = new HashMap<>();
// 元素工具类
private Elements elementUtils;
// 类型工具类
private Types typeUtils;
// 日志工具
private Messager messager;
// 文件生成器
private Filer filer;
// 资源包名
private String resourcePackageName;
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
messager = env.getMessager();
filer = env.getFiler();
// 初始化配置
initConfig(env);
}
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment roundEnv) {
// 处理各种Butterknife注解
processBindView(roundEnv);
processBindViews(roundEnv);
processBindArray(roundEnv);
processBindBool(roundEnv);
processBindColor(roundEnv);
processBindDimen(roundEnv);
processBindDrawable(roundEnv);
processBindInt(roundEnv);
processBindString(roundEnv);
processListeners(roundEnv);
// 生成绑定类
try {
for (Map.Entry<TypeElement, BindingSet> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(resourcePackageName);
javaFile.writeTo(filer);
}
} catch (IOException e) {
error(null, "Unable to write binding for type %s: %s",
targetClassMap.keySet(), e.getMessage());
}
return true;
}
// 处理@BindView注解
private void processBindView(RoundEnvironment roundEnv) {
Map<TypeElement, List<FieldViewBinding>> bindings = findAndParseFields(roundEnv,
BindView.class, new FieldCollectionViewBinder());
for (Map.Entry<TypeElement, List<FieldViewBinding>> entry : bindings.entrySet()) {
TypeElement typeElement = entry.getKey();
List<FieldViewBinding> viewBindings = entry.getValue();
BindingSet binding = findOrCreateBindingSet(typeElement);
for (FieldViewBinding viewBinding : viewBindings) {
binding.addField(viewBinding);
}
}
}
// 其他注解处理方法...
}
4.2 Butterknife注解处理器的初始化
在ButterKnifeProcessor的init方法中,进行了一些必要的初始化工作:
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
messager = env.getMessager();
filer = env.getFiler();
// 初始化配置
initConfig(env);
}
private void initConfig(ProcessingEnvironment env) {
// 获取资源包名配置
String rPackageOption = env.getOptions().get("butterknife.r_package");
if (rPackageOption != null && !rPackageOption.trim().isEmpty()) {
resourcePackageName = rPackageOption.trim();
return;
}
// 尝试从AndroidManifest.xml中获取包名
try {
resourcePackageName = findResourcePackageName();
} catch (IOException e) {
error(null, "Unable to find resource package name: %s", e.getMessage());
// Fallback to using the target package.
resourcePackageName = null;
}
}
4.3 处理@BindView注解
processBindView方法负责处理@BindView注解:
private void processBindView(RoundEnvironment roundEnv) {
// 查找并解析所有被@BindView注解的字段
Map<TypeElement, List<FieldViewBinding>> bindings = findAndParseFields(roundEnv,
BindView.class, new FieldCollectionViewBinder());
// 遍历每个类及其对应的字段绑定信息
for (Map.Entry<TypeElement, List<FieldViewBinding>> entry : bindings.entrySet()) {
TypeElement typeElement = entry.getKey();
List<FieldViewBinding> viewBindings = entry.getValue();
// 查找或创建绑定集合
BindingSet binding = findOrCreateBindingSet(typeElement);
// 将字段绑定信息添加到绑定集合中
for (FieldViewBinding viewBinding : viewBindings) {
binding.addField(viewBinding);
}
}
}
4.4 查找并解析注解字段
findAndParseFields方法是一个通用方法,用于查找并解析各种注解字段:
private Map<TypeElement, List<FieldViewBinding>> findAndParseFields(
RoundEnvironment roundEnv, Class<? extends Annotation> annotationClass,
FieldCollectionViewBinder fieldCollectionViewBinder) {
// 存储每个类及其对应的字段绑定信息
Map<TypeElement, List<FieldViewBinding>> targetClassMap = new LinkedHashMap<>();
// 存储已处理的资源ID
Set<Element> elements = roundEnv.getElementsAnnotatedWith(annotationClass);
// 遍历所有被注解的元素
for (Element element : elements) {
try {
// 验证元素是否为字段
if (!element.getKind().isField()) {
error(element, "@%s must be on a field.", annotationClass.getSimpleName());
continue;
}
VariableElement fieldElement = (VariableElement) element;
// 获取字段所在的类
TypeElement enclosingElement = (TypeElement) fieldElement.getEnclosingElement();
// 解析注解信息
List<FieldViewBinding> viewBindings =
parseField(fieldElement, annotationClass, fieldCollectionViewBinder);
// 将解析结果添加到映射中
if (viewBindings != null) {
targetClassMap.computeIfAbsent(enclosingElement, k -> new ArrayList<>())
.addAll(viewBindings);
}
} catch (Exception e) {
// 记录错误信息
error(element, "Unable to parse @%s field. (%s.%s)",
annotationClass.getSimpleName(),
element.getEnclosingElement().getSimpleName(),
element.getSimpleName(), e);
}
}
return targetClassMap;
}
4.5 解析单个字段
parseField方法用于解析单个字段的注解信息:
private List<FieldViewBinding> parseField(VariableElement element,
Class<? extends Annotation> annotationClass,
FieldCollectionViewBinder fieldCollectionViewBinder) {
// 获取字段类型
TypeMirror elementType = element.asType();
// 检查字段是否为private或static
if (isPrivate(element) || isStatic(element)) {
error(element, "@%s fields must not be private or static. (%s.%s)",
annotationClass.getSimpleName(),
element.getEnclosingElement().getSimpleName(), element.getSimpleName());
return null;
}
// 处理@BindView注解
if (annotationClass == BindView.class) {
BindView bindView = element.getAnnotation(BindView.class);
int id = bindView.value();
// 检查资源ID是否有效
if (id == View.NO_ID) {
error(element, "@BindView annotation must specify a valid view ID. (%s.%s)",
element.getEnclosingElement().getSimpleName(), element.getSimpleName());
return null;
}
// 检查字段类型是否可分配给目标类型
TypeMirror viewType = findViewByIdClass(elementType);
if (viewType == null) {
error(element, "@BindView field type must extend from View or be an interface. (%s.%s)",
element.getEnclosingElement().getSimpleName(), element.getSimpleName());
return null;
}
// 创建字段绑定信息
String name = element.getSimpleName().toString();
return Collections.singletonList(new FieldViewBinding(
id, name, elementType, false, false));
}
// 处理其他类型的注解...
return null;
}
4.6 生成绑定类
在处理完所有注解后,Butterknife会生成相应的绑定类:
// 在ButterKnifeProcessor的process方法中
try {
for (Map.Entry<TypeElement, BindingSet> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 生成Java文件
JavaFile javaFile = binding.brewJava(resourcePackageName);
// 写入文件
javaFile.writeTo(filer);
}
} catch (IOException e) {
error(null, "Unable to write binding for type %s: %s",
targetClassMap.keySet(), e.getMessage());
}
4.7 BindingSet类的实现
BindingSet类表示一个类的所有绑定信息,它负责生成绑定类的代码:
final class BindingSet {
private final TypeElement targetType;
private final List<FieldViewBinding> fieldBindings;
private final List<MethodViewBinding> methodBindings;
private final boolean isFinal;
private final BindingSet parentBinding;
// 生成Java文件
JavaFile brewJava(String rClass) {
return JavaFile.builder(getPackageName(), createTypeSpec(rClass))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
// 创建类型规范
private TypeSpec createTypeSpec(String rClass) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName())
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
if (parentBinding != null) {
result.superclass(ClassName.bestGuess(parentBinding.bindingClassName()));
} else {
result.addSuperinterface(ClassName.get("butterknife", "Unbinder"));
}
// 添加字段
if (parentBinding == null) {
result.addField(FieldSpec.builder(
ClassName.get("butterknife", "Unbinder"), "EMPTY_UNBINDER")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("new Unbinder() {\n"
+ " @Override public void unbind() {}\n"
+ "}")
.build());
}
// 添加构造函数
result.addMethod(createBindingConstructor(rClass));
// 添加unbind方法
if (parentBinding == null) {
result.addMethod(createUnbindMethod());
}
// 添加绑定方法
if (!fieldBindings.isEmpty()) {
result.addMethods(createFieldBindings());
}
// 添加事件绑定方法
if (!methodBindings.isEmpty()) {
result.addMethods(createMethodBindings());
}
return result.build();
}
// 创建绑定构造函数
private MethodSpec createBindingConstructor(String rClass) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC);
// 添加参数
constructor.addParameter(TypeName.OBJECT, "target", Modifier.FINAL);
constructor.addParameter(ClassName.get("android.view", "View"), "source");
// 添加父类构造函数调用
if (parentBinding != null) {
constructor.addStatement("super(target, source)");
} else {
constructor.addStatement("this.target = target");
}
// 添加字段绑定代码
for (FieldViewBinding fieldBinding : fieldBindings) {
if (parentBinding == null || !parentBinding.hasFieldBinding(fieldBinding.getId())) {
addFieldBinding(constructor, fieldBinding, rClass);
}
}
// 添加事件绑定代码
for (MethodViewBinding methodBinding : methodBindings) {
if (parentBinding == null || !parentBinding.hasMethodBinding(methodBinding)) {
addMethodBinding(constructor, methodBinding, rClass);
}
}
return constructor.build();
}
// 其他方法...
}
4.8 生成字段绑定代码
在BindingSet类中,addFieldBinding方法用于生成字段绑定的代码:
// 添加字段绑定代码
private void addFieldBinding(MethodSpec.Builder constructor, FieldViewBinding binding, String rClass) {
// 获取资源ID的名称
String idName = getResourceName(binding.getId(), rClass);
// 生成findViewById代码
constructor.addStatement("target.$N = ($T) source.findViewById($L)",
binding.getName(),
binding.getType(),
idName);
}
这段代码会生成类似下面的Java代码:
target.textView = (android.widget.TextView) source.findViewById(R.id.text_view);
4.9 生成事件绑定代码
addMethodBinding方法用于生成事件绑定的代码:
// 添加方法绑定代码
private void addMethodBinding(MethodSpec.Builder constructor, MethodViewBinding binding, String rClass) {
// 获取事件处理程序类型
EventHandler handler = binding.getEventHandler();
// 获取资源ID的名称
String idName = getResourceName(binding.getId(), rClass);
// 获取事件处理方法的名称
String methodName = binding.getMethod().getSimpleName().toString();
// 生成事件监听器设置代码
constructor.beginControlFlow("if (source.findViewById($L) != null)", idName);
// 根据不同的事件类型生成不同的监听器代码
switch (handler) {
case CLICK:
constructor.addStatement("source.findViewById($L).setOnClickListener(\n"
+ " new android.view.View.OnClickListener() {\n"
+ " @Override public void onClick(android.view.View p0) {\n"
+ " ((($T) target)).$N(p0);\n"
+ " }\n"
+ " })",
idName,
binding.getTargetType(),
methodName);
break;
case LONG_CLICK:
constructor.addStatement("source.findViewById($L).setOnLongClickListener(\n"
+ " new android.view.View.OnLongClickListener() {\n"
+ " @Override public boolean onLongClick(android.view.View p0) {\n"
+ " return ((($T) target)).$N(p0);\n"
+ " }\n"
+ " })",
idName,
binding.getTargetType(),
methodName);
break;
// 其他事件类型的处理...
default:
throw new IllegalStateException("Unknown event handler: " + handler);
}
constructor.endControlFlow();
}
对于@OnClick注解,会生成类似下面的Java代码:
if (source.findViewById(R.id.button) != null) {
source.findViewById(R.id.button).setOnClickListener(
new android.view.View.OnClickListener() {
@Override public void onClick(android.view.View p0) {
((MainActivity) target).onButtonClick(p0);
}
});
}
4.10 资源ID处理
Butterknife注解处理器需要处理资源ID,将其转换为对应的资源名称。这是通过getResourceName方法实现的:
// 获取资源名称
private String getResourceName(int id, String rClass) {
// 尝试从缓存中获取资源名称
String cachedName = R_CLASS_MEMBERS.get(id);
if (cachedName != null) {
return cachedName;
}
// 如果没有缓存,则生成资源引用
String packageName = rClass != null ? rClass : getPackageName();
String resourceName = "R.id." + id;
// 尝试解析资源名称
try {
// 通过反射获取资源类中的字段
Class<?> rClass = Class.forName(packageName + ".R");
for (Class<?> innerClass : rClass.getDeclaredClasses()) {
if (innerClass.getSimpleName().equals("id")) {
for (Field field : innerClass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& Modifier.isPublic(field.getModifiers())
&& field.getType() == int.class) {
try {
int value = field.getInt(null);
if (value == id) {
resourceName = packageName + ".R.id." + field.getName();
R_CLASS_MEMBERS.put(id, resourceName);
return resourceName;
}
} catch (IllegalAccessException e) {
// 忽略
}
}
}
break;
}
}
} catch (ClassNotFoundException e) {
// 忽略
}
return resourceName;
}
4.11 父类绑定处理
Butterknife会处理继承关系,确保父类的绑定也能正确工作。这是通过findParentBinding方法实现的:
// 查找父类的绑定
private BindingSet findParentBinding(TypeElement typeElement) {
// 获取父类类型
TypeMirror superclassType = typeElement.getSuperclass();
// 如果父类是Object,则没有父类绑定
if (superclassType == null ||
typeUtils.isSameType(superclassType, elementUtils.getTypeElement("java.lang.Object").asType())) {
return null;
}
TypeElement superclassElement = (TypeElement) typeUtils.asElement(superclassType);
// 检查父类是否有Butterknife注解
boolean hasAnnotation = false;
for (Element element : superclassElement.getEnclosedElements()) {
if (element.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) element;
if (field.getAnnotation(BindView.class) != null ||
field.getAnnotation(BindViews.class) != null) {
hasAnnotation = true;
break;
}
} else if (element.getKind() == ElementKind.METHOD) {
ExecutableElement method = (ExecutableElement) element;
if (method.getAnnotation(OnClick.class) != null ||
method.getAnnotation(OnLongClick.class) != null) {
hasAnnotation = true;
break;
}
}
}
// 如果父类有注解,则递归查找父类的绑定
if (hasAnnotation) {
BindingSet parentBinding = targetClassMap.get(superclassElement);
if (parentBinding != null) {
return parentBinding;
}
return findParentBinding(superclassElement);
}
return null;
}
4.12 处理多重注解
Butterknife支持在同一个视图上应用多个事件注解,例如同时应用@OnClick和@OnLongClick。这是通过在生成的绑定类中为同一个视图设置多个监听器来实现的。
下面是处理多重注解的相关代码:
// 在BindingSet类中处理多重注解
private void addMethodBinding(MethodSpec.Builder constructor, MethodViewBinding binding, String rClass) {
// 获取资源ID
int id = binding.getId();
// 检查是否已经为该ID添加过监听器
if (methodBindingsByView.containsKey(id)) {
// 如果已经添加过监听器,则将此方法绑定添加到现有监听器中
MultiListenerBinding multiListener = methodBindingsByView.get(id);
multiListener.addMethodBinding(binding);
} else {
// 如果没有添加过监听器,则创建新的监听器
MultiListenerBinding multiListener = new MultiListenerBinding(id);
multiListener.addMethodBinding(binding);
methodBindingsByView.put(id, multiListener);
// 添加监听器代码
addMultiListenerBinding(constructor, multiListener, rClass);
}
}
// 添加多重监听器绑定
private void addMultiListenerBinding(MethodSpec.Builder constructor, MultiListenerBinding multiListener, String rClass) {
String idName = getResourceName(multiListener.getId(), rClass);
constructor.beginControlFlow("if (source.findViewById($L) != null)", idName);
// 根据不同的事件类型生成不同的监听器代码
// 这里会为同一个视图设置多个监听器
// 例如,同时设置点击和长按监听器
if (multiListener.hasClickBinding()) {
constructor.addStatement("source.findViewById($L).setOnClickListener(\n"
+ " new android.view.View.OnClickListener() {\n"
+ " @Override public void onClick(android.view.View p0) {\n"
+ " $L\n"
+ " }\n"
+ " })",
idName,
generateClickHandlerCode(multiListener.getClickBindings()));
}
if (multiListener.hasLongClickBinding()) {
constructor.addStatement("source.findViewById($L).setOnLongClickListener(\n"
+ " new android.view.View.OnLongClickListener() {\n"
+ " @Override public boolean onLongClick(android.view.View p0) {\n"
+ " return $L\n"
+ " }\n"
+ " })",
idName,
generateLongClickHandlerCode(multiListener.getLongClickBindings()));
}
constructor.endControlFlow();
}
4.13 生成的绑定类示例
通过Butterknife注解处理器生成的绑定类类似于下面的代码:
// 生成的MainActivity_ViewBinding类
package com.example.myapplication;
import android.view.View;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import java.lang.Override;
import java.lang.SuppressWarnings;
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f08005a;
@SuppressWarnings("unchecked")
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
View view;
target.textView = (android.widget.TextView) source.findViewById(R.id.text_view);
view = source.findViewById(R.id.button);
view7f08005a = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onButtonClick(p0);
}
});
}
@Override
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.textView = null;
view7f08005a.setOnClickListener(null);
view7f08005a = null;
}
}
4.14 资源绑定处理
除了视图绑定,Butterknife还支持资源绑定,如字符串、颜色、尺寸等。下面是处理资源绑定的代码:
// 处理@BindString注解
private void processBindString(RoundEnvironment roundEnv) {
Map<TypeElement, List<FieldResourceBinding>> bindings = findAndParseFields(roundEnv,
BindString.class, new ResourceCollectionViewBinder());
for (Map.Entry<TypeElement, List<FieldResourceBinding>> entry : bindings.entrySet()) {
TypeElement typeElement = entry.getKey();
List<FieldResourceBinding> viewBindings = entry.getValue();
BindingSet binding = findOrCreateBindingSet(typeElement);
for (FieldResourceBinding viewBinding : viewBindings) {
binding.addFieldResource(viewBinding);
}
}
}
// 处理@BindColor注解
private void processBindColor(RoundEnvironment roundEnv) {
Map<TypeElement, List<FieldResourceBinding>> bindings = findAndParseFields(roundEnv,
BindColor.class, new ResourceCollectionViewBinder());
for (Map.Entry<TypeElement, List<FieldResourceBinding>> entry : bindings.entrySet()) {
TypeElement typeElement = entry.getKey();
List<FieldResourceBinding> viewBindings = entry.getValue();
BindingSet binding = findOrCreateBindingSet(typeElement);
for (FieldResourceBinding viewBinding : viewBindings) {
binding.addFieldResource(viewBinding);
}
}
}
// 其他资源注解处理方法...
4.15 事件绑定处理
Butterknife支持多种事件绑定,包括点击、长按、文本变化等。下面是处理事件绑定的代码:
// 处理所有事件监听器注解
private void processListeners(RoundEnvironment roundEnv) {
// 处理@OnClick注解
processOnClick(roundEnv);
// 处理@OnLongClick注解
processOnLongClick(roundEnv);
// 处理@OnTextChanged注解
processOnTextChanged(roundEnv);
// 处理其他事件注解...
}
// 处理@OnClick注解
private void processOnClick(RoundEnvironment roundEnv) {
Map<TypeElement, List<MethodViewBinding>> bindings = findAndParseMethods(roundEnv,
OnClick.class, new OnClickResponseHandler());
for (Map.Entry<TypeElement, List<MethodViewBinding>> entry : bindings.entrySet()) {
TypeElement typeElement = entry.getKey();
List<MethodViewBinding> methodBindings = entry.getValue();
BindingSet binding = findOrCreateBindingSet(typeElement);
for (MethodViewBinding methodBinding : methodBindings) {
binding.addMethod(methodBinding);
}
}
}
// 处理@OnLongClick注解
private void processOnLongClick(RoundEnvironment roundEnv) {
Map<TypeElement, List<MethodViewBinding>> bindings = findAndParseMethods(roundEnv,
OnLongClick.class, new OnLongClickResponseHandler());
for (Map.Entry<TypeElement, List<MethodViewBinding>> entry : bindings.entrySet()) {
TypeElement typeElement = entry.getKey();
List<MethodViewBinding> methodBindings = entry.getValue();
BindingSet binding = findOrCreateBindingSet(typeElement);
for (MethodViewBinding methodBinding : methodBindings) {
binding.addMethod(methodBinding);
}
}
}
// 其他事件处理方法...
4.16 方法参数验证
Butterknife会验证事件处理方法的参数是否正确。下面是验证方法参数的代码:
// 验证方法参数
private boolean validateMethodArguments(ExecutableElement element,
Annotation annotation, ResponseHandler handler, Messager messager) {
List<? extends VariableElement> parameters = element.getParameters();
int count = parameters.size();
// 检查参数数量是否符合要求
if (count > handler.getMaxParameters()) {
error(element, "@%s methods can have at most %s parameter(s). (%s.%s)",
annotation.annotationType().getSimpleName(),
handler.getMaxParameters(),
element.getEnclosingElement().getSimpleName(),
element.getSimpleName());
return false;
}
// 检查每个参数的类型
for (int i = 0; i < count; i++) {
VariableElement parameter = parameters.get(i);
TypeMirror type = parameter.asType();
// 检查参数类型是否符合要求
if (!handler.isValidParameter(type, i)) {
error(element, "@%s method parameter #%s must be %s. (%s.%s)",
annotation.annotationType().getSimpleName(),
i + 1,
handler.getParameterTypeName(i),
element.getEnclosingElement().getSimpleName(),
element.getSimpleName());
return false;
}
}
return true;
}
4.17 错误处理
Butterknife注解处理器会处理各种错误情况,并向用户提供有用的错误信息。下面是错误处理的代码:
// 输出错误信息
private void error(Element element, String message, Object... args) {
if (args.length > 0) {
message = String.format(message, args);
}
messager.printMessage(Diagnostic.Kind.ERROR, message, element);
}
// 输出警告信息
private void warning(Element element, String message, Object... args) {
if (args.length > 0) {
message = String.format(message, args);
}
messager.printMessage(Diagnostic.Kind.WARNING, message, element);
}
// 验证元素是否为private
private boolean isPrivate(Element element) {
return element.getModifiers().contains(Modifier.PRIVATE);
}
// 验证元素是否为static
private boolean isStatic(Element element) {
return element.getModifiers().contains(Modifier.STATIC);
}
4.18 处理抽象类和接口
Butterknife可以处理抽象类和接口中的注解。当在抽象类或接口中定义了注解方法时,Butterknife会在实现类的绑定类中生成相应的代码。
下面是处理抽象类和接口的代码:
// 检查是否为抽象类
private boolean isAbstract(TypeElement type) {
return type.getModifiers().contains(Modifier.ABSTRACT);
}
// 检查是否为接口
private boolean isInterface(TypeElement type) {
return type.getKind() == ElementKind.INTERFACE;
}
// 在生成绑定类时处理抽象类和接口
private void generateBindingClassForType(TypeElement typeElement) {
// 检查是否为抽象类或接口
boolean isAbstractType = isAbstract(typeElement);
boolean isInterfaceType = isInterface(typeElement);
// 创建绑定类
BindingSet bindingSet = BindingSet.create(typeElement, isAbstractType, isInterfaceType);
// 处理类中的所有注解
processBindViewAnnotations(typeElement, bindingSet);
processBindStringAnnotations(typeElement, bindingSet);
processOnClickAnnotations(typeElement, bindingSet);
// 处理其他注解...
// 生成绑定类代码
JavaFile javaFile = bindingSet.brewJava();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Failed to write binding class for type %s: %s",
typeElement.getQualifiedName(), e.getMessage());
}
}
4.19 处理泛型类型
Butterknife可以处理泛型类型,确保在泛型类中使用注解时能够正确生成绑定代码。
下面是处理泛型类型的代码:
// 处理泛型类型
private TypeMirror resolveType(TypeMirror typeMirror) {
// 如果是参数化类型(泛型类型)
if (typeMirror instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) typeMirror;
TypeElement typeElement = (TypeElement) typeUtils.asElement(parameterizedType.getRawType());
// 解析泛型参数
List<? extends TypeMirror> typeArguments = parameterizedType.getTypeArguments();
Map<String, TypeMirror> typeVariableMap = new HashMap<>();
// 建立类型变量到实际类型的映射
List<? extends TypeParameterElement> typeParameters = typeElement.getTypeParameters();
for (int i = 0; i < typeParameters.size() && i < typeArguments.size(); i++) {
typeVariableMap.put(typeParameters.get(i).getSimpleName().toString(), typeArguments.get(i));
}
// 解析类型中的泛型变量
return resolveTypeWithTypeVariables(typeMirror, typeVariableMap);
}
return typeMirror;
}
// 使用类型变量映射解析类型
private TypeMirror resolveTypeWithTypeVariables(TypeMirror typeMirror,
Map<String, TypeMirror> typeVariableMap) {
// 如果是类型变量
if (typeMirror instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) typeMirror;
String name = typeVariable.asElement().getSimpleName().toString();
// 检查是否在映射中
if (typeVariableMap.containsKey(name)) {
return typeVariableMap.get(name);
}
}
// 递归处理类型中的泛型参数
if (typeMirror instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) typeMirror;
List<TypeMirror> resolvedTypeArguments = new ArrayList<>();
for (TypeMirror typeArgument : parameterizedType.getTypeArguments()) {
resolvedTypeArguments.add(resolveTypeWithTypeVariables(typeArgument, typeVariableMap));
}
// 构建解析后的参数化类型
return typeUtils.getDeclaredType(
(TypeElement) typeUtils.asElement(parameterizedType.getRawType()),
resolvedTypeArguments.toArray(new TypeMirror[0]));
}
return typeMirror;
}
4.20 处理嵌套类
Butterknife可以处理嵌套类中的注解,确保在嵌套类中使用注解时能够正确生成绑定代码。
下面是处理嵌套类的代码:
// 处理嵌套类
private void processNestedClasses(TypeElement typeElement, BindingSet bindingSet) {
// 获取所有嵌套类
List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
for (Element enclosedElement : enclosedElements) {
if (enclosedElement.getKind() == ElementKind.CLASS ||
enclosedElement.getKind() == ElementKind.INTERFACE) {
TypeElement nestedTypeElement = (TypeElement) enclosedElement;
// 处理嵌套类中的注解
processBindViewAnnotations(nestedTypeElement, bindingSet);
processBindStringAnnotations(nestedTypeElement, bindingSet);
processOnClickAnnotations(nestedTypeElement, bindingSet);
// 处理其他注解...
// 递归处理嵌套类中的嵌套类
processNestedClasses(nestedTypeElement, bindingSet);
}
}
}
4.21 处理继承关系
Butterknife会处理类的继承关系,确保父类中的注解也能正确生效。
下面是处理继承关系的代码:
// 处理继承关系
private void processInheritance(TypeElement typeElement, BindingSet bindingSet) {
// 获取父类
TypeMirror superclassType = typeElement.getSuperclass();
// 如果父类不是Object
if (!typeUtils.isSameType(superclassType, elementUtils.getTypeElement("java.lang.Object").asType())) {
TypeElement superclassElement = (TypeElement) typeUtils.asElement(superclassType);
// 处理父类中的注解
processBindViewAnnotations(superclassElement, bindingSet);
processBindStringAnnotations(superclassElement, bindingSet);
processOnClickAnnotations(superclassElement, bindingSet);
// 处理其他注解...
// 递归处理父类的父类
processInheritance(superclassElement, bindingSet);
}
}
4.22 处理库项目中的注解
Butterknife可以处理库项目中的注解,确保在库项目中使用注解时能够正确生成绑定代码。
下面是处理库项目中注解的代码:
// 检查是否为库项目
private boolean isLibraryProject() {
// 通过检查是否存在R文件中的BR类来判断是否为库项目
try {
elementUtils.getTypeElement("androidx.databinding.library.baseAdapters.BR");
return true;
} catch (Exception e) {
return false;
}
}
// 处理库项目中的注解
private void processLibraryAnnotations(RoundEnvironment roundEnv, BindingSet bindingSet) {
if (isLibraryProject()) {
// 处理库项目中的特殊注解
processLibraryBindViewAnnotations(roundEnv, bindingSet);
processLibraryBindStringAnnotations(roundEnv, bindingSet);
// 处理其他库项目中的注解...
}
}
4.23 处理资源引用
Butterknife可以处理资源引用,确保在注解中使用资源ID时能够正确解析。
下面是处理资源引用的代码:
// 解析资源ID
private int parseResourceId(Element element, AnnotationMirror annotationMirror, String valueName) {
// 获取注解中的value值
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {
if (entry.getKey().getSimpleName().toString().equals(valueName)) {
AnnotationValue value = entry.getValue();
Object objValue = value.getValue();
// 检查是否为int类型
if (objValue instanceof Integer) {
return (Integer) objValue;
} else if (objValue instanceof String) {
// 尝试解析字符串形式的资源ID
return parseResourceIdFromString(element, (String) objValue);
}
}
}
return View.NO_ID;
}
// 从字符串解析资源ID
private int parseResourceIdFromString(Element element, String resourceName) {
// 尝试从R文件中查找资源ID
try {
// 获取R类
TypeElement rClassElement = elementUtils.getTypeElement("com.example.R");
if (rClassElement == null) {
error(element, "Unable to find R class");
return View.NO_ID;
}
// 获取R.id类
TypeElement rIdClassElement = null;
for (Element enclosedElement : rClassElement.getEnclosedElements()) {
if (enclosedElement.getKind() == ElementKind.CLASS &&
enclosedElement.getSimpleName().toString().equals("id")) {
rIdClassElement = (TypeElement) enclosedElement;
break;
}
}
if (rIdClassElement == null) {
error(element, "Unable to find R.id class");
return View.NO_ID;
}
// 查找资源ID字段
for (Element enclosedElement : rIdClassElement.getEnclosedElements()) {
if (enclosedElement.getKind() == ElementKind.FIELD &&
enclosedElement.getSimpleName().toString().equals(resourceName)) {
VariableElement fieldElement = (VariableElement) enclosedElement;
AnnotationValue value = processingEnv.getElementUtils().getConstantExpression(fieldElement);
if (value != null && value.getValue() instanceof Integer) {
return (Integer) value.getValue();
}
}
}
} catch (Exception e) {
error(element, "Error parsing resource ID: %s", e.getMessage());
}
return View.NO_ID;
}
4.24 处理不同的Android Gradle插件版本
Butterknife注解处理器需要处理不同版本的Android Gradle插件,确保在各种环境下都能正常工作。
下面是处理不同Android Gradle插件版本的代码:
// 获取Android Gradle插件版本
private String getAndroidGradlePluginVersion() {
try {
// 通过反射获取Android Gradle插件版本
Class<?> versionClass = Class.forName("com.android.builder.Version");
Field field = versionClass.getField("ANDROID_GRADLE_PLUGIN_VERSION");
return (String) field.get(null);
} catch (Exception e) {
// 如果获取失败,返回默认版本
return "4.0.0";
}
}
// 根据Android Gradle插件版本执行不同的处理逻辑
private void processBasedOnAndroidGradlePluginVersion(RoundEnvironment roundEnv) {
String version = getAndroidGradlePluginVersion();
// 解析版本号
String[] versionParts = version.split("\\.");
int majorVersion = Integer.parseInt(versionParts[0]);
int minorVersion = Integer.parseInt(versionParts[1]);
// 根据不同版本执行不同的处理逻辑
if (majorVersion >= 7) {
// 处理AGP 7.0及以上版本
processForAgp7(roundEnv);
} else if (majorVersion >= 4) {
// 处理AGP 4.0-6.9版本
processForAgp4To6(roundEnv);
} else {
// 处理AGP 3.x及以下版本
processForAgp3(roundEnv);
}
}
4.25 性能优化
Butterknife注解处理器在设计上进行了多种性能优化,确保在处理大量注解时仍然高效。
下面是一些性能优化的代码示例:
// 使用缓存提高性能
private final Map<TypeElement, BindingSet> bindingSetCache = new HashMap<>();
// 获取或创建BindingSet,使用缓存避免重复处理
private BindingSet getOrCreateBindingSet(TypeElement typeElement) {
BindingSet bindingSet = bindingSetCache.get(typeElement);
if (bindingSet == null) {
bindingSet = BindingSet.create(typeElement);
bindingSetCache.put(typeElement, bindingSet);
}
return bindingSet;
}
// 批量处理注解元素,减少遍历次数
private void processAnnotationsInBatches(Set<? extends Element> elements) {
// 将元素按类型分组
Map<Class<? extends Annotation>, List<Element>> elementsByAnnotation = new HashMap<>();
for (Element element : elements) {
for (Class<? extends Annotation> annotationClass : SUPPORTED_ANNOTATIONS) {
if (element.getAnnotation(annotationClass) != null) {
elementsByAnnotation.computeIfAbsent(annotationClass, k -> new ArrayList<>())
.add(element);
break;
}
}
}
// 批量处理每组注解
for (Map.Entry<Class<? extends Annotation>, List<Element>> entry : elementsByAnnotation.entrySet()) {
processAnnotationBatch(entry.getKey(), entry.getValue());
}
}
4.26 与Kotlin集成
Butterknife可以与Kotlin集成,支持在Kotlin代码中使用注解。下面是与Kotlin集成的相关代码:
// 检查是否为Kotlin文件
private boolean isKotlinFile(Element element) {
// 通过检查元素的注解判断是否为Kotlin文件
return element.getAnnotation(Metadata.class) != null;
}
// 处理Kotlin属性
private void processKotlinProperty(VariableElement element, BindingSet bindingSet) {
if (!isKotlinFile(element)) {
return;
}
// 获取Kotlin属性的相关信息
AnnotationMirror kotlinMetadata = getAnnotationMirror(element, Metadata.class);
if (kotlinMetadata == null) {
return;
}
// 解析Kotlin属性信息
Map<String, Object> metadata = getAnnotationValues(kotlinMetadata);
String kind = (String) metadata.get("kind");
// 处理不同类型的Kotlin属性
if ("property".equals(kind)) {
// 处理普通Kotlin属性
processRegularKotlinProperty(element, bindingSet);
} else if ("delegate".equals(kind)) {
// 处理委托属性
processDelegatedKotlinProperty(element, bindingSet);
}
}
// 获取注解镜像
private AnnotationMirror getAnnotationMirror(Element element, Class<?> annotationClass) {
String annotationClassName = annotationClass.getName();
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
if (annotationMirror.getAnnotationType().toString().equals(annotationClassName)) {
return annotationMirror;
}
}
return null;
}
// 获取注解值
private Map<String, Object> getAnnotationValues(AnnotationMirror annotationMirror) {
Map<String, Object> values = new HashMap<>();
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {
values.put(entry.getKey().getSimpleName().toString(), entry.getValue().getValue());
}
return values;
}
4.27 处理R文件引用
Butterknife需要正确处理R文件的引用,确保在不同的Android项目结构中都能正常工作。
下面是处理R文件引用的代码:
// 获取R类的名称
private String getRClassName(Element element) {
// 尝试从AndroidManifest.xml获取包名
String packageName = getPackageNameFromManifest(element);
if (packageName != null) {
return packageName + ".R";
}
// 如果无法从清单文件获取,则使用元素所在的包
return processingEnv.getElementUtils().getPackageOf(element).getQualifiedName() + ".R";
}
// 从AndroidManifest.xml获取包名
private String getPackageNameFromManifest(Element element) {
try {
// 获取项目根目录
File projectDir = new File(processingEnv.getOptions().get("projectDir"));
File manifestFile = new File(projectDir, "src/main/AndroidManifest.xml");
if (manifestFile.exists()) {
// 解析AndroidManifest.xml文件
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(manifestFile);
// 获取package属性
Element root = document.getDocumentElement();
return root.getAttribute("package");
}
} catch (Exception e) {
// 忽略异常
}
return null;
}
// 处理R文件中的资源引用
private String processResourceReference(Element element, int resourceId) {
String rClassName = getRClassName(element);
// 尝试从R文件中查找资源名称
String resourceName = findResourceName(rClassName, resourceId);
if (resourceName != null) {
return rClassName + "." + resourceName;
}
// 如果找不到资源名称,则使用默认格式
return rClassName + ".id.unknown_" + resourceId;
}
// 查找资源名称
private String findResourceName(String rClassName, int resourceId) {
try {
// 加载R类
Class<?> rClass = Class.forName(rClassName);
// 查找内部类
for (Class<?> innerClass : rClass.getDeclaredClasses()) {
if (innerClass.getSimpleName().equals("id")) {
// 查找字段
for (Field field : innerClass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) &&
Modifier.isPublic(field.getModifiers()) &&
field.getType() == int.class) {
try {
int value = field.getInt(null);
if (value == resourceId) {
return "id." + field.getName();
}
} catch (IllegalAccessException e) {
// 忽略异常
}
}
}
}
}
} catch (ClassNotFoundException e) {
// 忽略异常
}
return null;
}
4.28 处理不同的构建变体
Butterknife需要处理不同的构建变体,确保在各种构建配置下都能正确生成绑定代码。
下面是处理不同构建变体的代码:
// 获取当前构建变体
private String getCurrentBuildVariant() {
// 尝试从Gradle属性获取构建变体
String variant = processingEnv.getOptions().get("android.injected.variant");
if (variant != null) {
return variant;
}
### 4.28 处理不同的构建变体(续)
```java
// 如果无法从Gradle属性获取,则尝试从其他地方获取
// 例如,通过解析项目结构或其他构建信息
return "debug"; // 默认返回debug变体
}
// 根据构建变体处理资源
private void processResourcesForVariant(Element element, int resourceId) {
String variant = getCurrentBuildVariant();
// 根据不同的构建变体处理资源
if (variant.contains("release")) {
// 处理release变体的资源
processReleaseVariantResource(element, resourceId);
} else {
// 处理debug或其他变体的资源
processDebugVariantResource(element, resourceId);
}
}
// 处理release变体的资源
private void processReleaseVariantResource(Element element, int resourceId) {
// 在release变体中,可能需要使用不同的资源处理策略
// 例如,使用混淆后的资源ID
String obfuscatedResourceId = getObfuscatedResourceId(element, resourceId);
if (obfuscatedResourceId != null) {
// 使用混淆后的资源ID
// ...
} else {
// 使用原始资源ID
// ...
}
}
// 处理debug变体的资源
private void processDebugVariantResource(Element element, int resourceId) {
// 在debug变体中,可以使用原始资源ID
// ...
}
// 获取混淆后的资源ID
private String getObfuscatedResourceId(Element element, int resourceId) {
// 在实际应用中,可能需要从混淆映射文件中查找混淆后的资源ID
// 这里简化处理,返回null表示未找到混淆后的ID
return null;
}
4.29 处理多模块项目
在多模块项目中,Butterknife需要确保不同模块之间的注解处理正确工作。
// 处理多模块项目中的跨模块引用
private void processCrossModuleReferences(TypeElement typeElement, BindingSet bindingSet) {
// 获取当前模块的包名
String currentModulePackage = processingEnv.getElementUtils()
.getPackageOf(typeElement).getQualifiedName().toString();
// 遍历所有字段,检查是否有跨模块的引用
for (VariableElement field : typeElement.getEnclosedElements()) {
if (field.getKind() != ElementKind.FIELD) {
continue;
}
// 检查字段是否有@BindView注解
BindView bindView = field.getAnnotation(BindView.class);
if (bindView == null) {
continue;
}
// 获取字段类型
TypeMirror fieldType = field.asType();
String fieldTypeName = fieldType.toString();
// 检查字段类型是否来自其他模块
if (!fieldTypeName.startsWith(currentModulePackage)) {
// 处理跨模块的视图绑定
processCrossModuleViewBinding(field, bindingSet);
}
}
}
// 处理跨模块的视图绑定
private void processCrossModuleViewBinding(VariableElement field, BindingSet bindingSet) {
// 获取资源ID
BindView bindView = field.getAnnotation(BindView.class);
int viewId = bindView.value();
// 获取资源名称
String resourceName = getResourceName(viewId);
// 获取字段类型
TypeMirror fieldType = field.asType();
// 生成跨模块的视图绑定代码
// 这里需要特殊处理,确保能够正确引用其他模块的视图类型
String qualifiedFieldType = fieldType.toString();
// 添加跨模块的视图绑定
bindingSet.addCrossModuleFieldBinding(
field.getSimpleName().toString(),
qualifiedFieldType,
resourceName);
}
// 获取资源名称
private String getResourceName(int resourceId) {
// 尝试从R文件中查找资源名称
// ...
// 如果找不到,则返回默认格式
return "R.id.unknown_" + resourceId;
}
4.30 处理嵌套布局
Butterknife能够处理嵌套布局中的视图绑定,确保子布局中的视图也能正确绑定。
// 处理嵌套布局
private void processNestedLayouts(TypeElement typeElement, BindingSet bindingSet) {
// 查找所有带有@BindView注解的字段
for (VariableElement field : typeElement.getEnclosedElements()) {
if (field.getKind() != ElementKind.FIELD) {
continue;
}
BindView bindView = field.getAnnotation(BindView.class);
if (bindView == null) {
continue;
}
// 获取字段类型
TypeMirror fieldType = field.asType();
TypeElement fieldTypeElement = (TypeElement) processingEnv.getTypeUtils()
.asElement(fieldType);
// 检查字段类型是否为ViewGroup或其子类
if (isSubtypeOfType(fieldType, "android.view.ViewGroup")) {
// 处理嵌套布局
processNestedLayout(field, bindingSet);
}
}
}
// 处理单个嵌套布局
private void processNestedLayout(VariableElement field, BindingSet bindingSet) {
// 获取资源ID
BindView bindView = field.getAnnotation(BindView.class);
int viewId = bindView.value();
// 获取字段名称
String fieldName = field.getSimpleName().toString();
// 生成嵌套布局的绑定代码
bindingSet.addNestedLayoutBinding(fieldName, viewId);
}
// 检查类型是否为另一个类型的子类型
private boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
if (otherType.equals(typeMirror.toString())) {
return true;
}
TypeMirror boxedType = boxPrimitiveType(typeMirror);
if (otherType.equals(boxedType.toString())) {
return true;
}
TypeElement element = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
if (element == null) {
return false;
}
TypeMirror superType = element.getSuperclass();
if (superType != null) {
if (isSubtypeOfType(superType, otherType)) {
return true;
}
}
for (TypeMirror interfaceType : element.getInterfaces()) {
if (isSubtypeOfType(interfaceType, otherType)) {
return true;
}
}
return false;
}
// 包装基本类型
private TypeMirror boxPrimitiveType(TypeMirror typeMirror) {
if (typeMirror.getKind().isPrimitive()) {
switch (typeMirror.getKind()) {
case BOOLEAN:
return processingEnv.getElementUtils().getTypeElement("java.lang.Boolean").asType();
case BYTE:
return processingEnv.getElementUtils().getTypeElement("java.lang.Byte").asType();
case CHAR:
return processingEnv.getElementUtils().getTypeElement("java.lang.Character").asType();
case DOUBLE:
return processingEnv.getElementUtils().getTypeElement("java.lang.Double").asType();
case FLOAT:
return processingEnv.getElementUtils().getTypeElement("java.lang.Float").asType();
case INT:
return processingEnv.getElementUtils().getTypeElement("java.lang.Integer").asType();
case LONG:
return processingEnv.getElementUtils().getTypeElement("java.lang.Long").asType();
case SHORT:
return processingEnv.getElementUtils().getTypeElement("java.lang.Short").asType();
default:
return typeMirror;
}
}
return typeMirror;
}
4.31 处理RecyclerView ViewHolder
Butterknife特别优化了对RecyclerView ViewHolder的支持,使其能够更高效地绑定ViewHolder中的视图。
// 处理RecyclerView ViewHolder
private void processViewHolder(TypeElement typeElement, BindingSet bindingSet) {
// 检查是否为ViewHolder的子类
if (!isSubtypeOfType(typeElement.asType(), "androidx.recyclerview.widget.RecyclerView.ViewHolder")) {
return;
}
// 处理ViewHolder中的所有@BindView注解
for (VariableElement field : typeElement.getEnclosedElements()) {
if (field.getKind() != ElementKind.FIELD) {
continue;
}
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
// 处理ViewHolder中的视图绑定
processViewHolderField(field, bindingSet);
}
}
// 处理ViewHolder中的事件绑定
processViewHolderEvents(typeElement, bindingSet);
}
// 处理ViewHolder中的字段
private void processViewHolderField(VariableElement field, BindingSet bindingSet) {
// 获取资源ID
BindView bindView = field.getAnnotation(BindView.class);
int viewId = bindView.value();
// 获取字段类型和名称
TypeMirror fieldType = field.asType();
String fieldName = field.getSimpleName().toString();
// 添加ViewHolder字段绑定
bindingSet.addViewHolderFieldBinding(fieldName, fieldType.toString(), viewId);
}
// 处理ViewHolder中的事件绑定
private void processViewHolderEvents(TypeElement typeElement, BindingSet bindingSet) {
// 查找所有事件注解方法
for (ExecutableElement method : typeElement.getEnclosedElements()) {
if (method.getKind() != ElementKind.METHOD) {
continue;
}
// 检查是否有事件注解
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
processViewHolderOnClick(method, bindingSet);
continue;
}
OnLongClick onLongClick = method.getAnnotation(OnLongClick.class);
if (onLongClick != null) {
processViewHolderOnLongClick(method, bindingSet);
continue;
}
// 处理其他事件注解...
}
}
// 处理ViewHolder中的OnClick注解
private void processViewHolderOnClick(ExecutableElement method, BindingSet bindingSet) {
OnClick onClick = method.getAnnotation(OnClick.class);
int[] viewIds = onClick.value();
for (int viewId : viewIds) {
// 添加ViewHolder点击事件绑定
bindingSet.addViewHolderClickBinding(
method.getSimpleName().toString(),
method.getParameters(),
viewId);
}
}
// 处理ViewHolder中的OnLongClick注解
private void processViewHolderOnLongClick(ExecutableElement method, BindingSet bindingSet) {
OnLongClick onLongClick = method.getAnnotation(OnLongClick.class);
int[] viewIds = onLongClick.value();
for (int viewId : viewIds) {
// 添加ViewHolder长按事件绑定
bindingSet.addViewHolderLongClickBinding(
method.getSimpleName().toString(),
method.getParameters(),
viewId);
}
}