ButterKnife
是使用注解的代码生成框架,在编译期生成代码,没有使用反射,不会造成运行时的性能问题
使用
使用ButterKnife
的例子
class ExampleActivity extends Activity {
@Bind(R.id.user)
EditText username;
@Bind(R.id.pass)
EditText password;
@BindString(R.string.login_error)
String loginErrorMessage;
@OnClick(R.id.submit)
void submit() {
...
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
...
}
}
注解
最基本的Bind.java
@Retention(CLASS)
@Target(FIELD)
public @interface Bind {
@IdRes int value();
}
定义的Bind
注解,存在于编译期,目标是FIELD
,提供一个资源ID
处理
处理过程是在ButterKnifeProcessor.java
中
AbstractProcessor
是Processor
的子类,就是注解处理器
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
try {
JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
Writer writer = jfo.openWriter();
writer.write(bindingClass.brewJava());
writer.flush();
writer.close();
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement, e.getMessage());
}
}
return true;
}
}
关键方法是process()
首先findAndParseTargets()
解析注解,保存到一个表里面,然后遍历表,自动生成类似ExampleActivity$$ViewBinder
这样的类文件
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<TypeElement, BindingClass>();
Set<String> erasedTargetNames = new LinkedHashSet<String>();
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
try {
parseBind(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, Bind.class, e);
}
}
......
}
挨个处理每个注解,在parseBind()
中对Bind
这个注解进行处理
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<String> erasedTargetNames) {
// 判断一下是否可以处理
if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element) || isBindingInWrongPackage(Bind.class, element)) {
return;
}
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.ARRAY) {
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (LIST_TYPE.equals(doubleErasure(elementType))) {
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(), ((TypeElement) element.getEnclosingElement()).getQualifiedName(), element.getSimpleName());
} else {
parseBindOne(element, targetClassMap, erasedTargetNames);
}
}
一般也就对应一个,进入parseBindOne()
private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<String> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 验证是不是View的子类
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)", Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// 获得id,明显资源id只能是一个
int[] ids = element.getAnnotation(Bind.class).value();
if (ids.length != 1) {
error(element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)", Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
if (hasError) {
return;
}
int id = ids[0];
// 获取生成的绑定类
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass != null) {
// 获取对应id的元素,看有没有,有的话就不用重复添加
ViewBindings viewBindings = bindingClass.getViewBinding(id);
if (viewBindings != null) {
Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
if (iterator.hasNext()) {
FieldViewBinding existingBinding = iterator.next();
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
Bind.class.getSimpleName(), id, existingBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
} else {
// 获取或创建绑定类
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
}
// 根据注解信息来生成注入关系
String name = element.getSimpleName().toString();
String type = elementType.toString();
boolean required = isRequiredBinding(element);
FieldViewBinding binding = new FieldViewBinding(name, type, required);
bindingClass.addField(id, binding);
erasedTargetNames.add(enclosingElement.toString());
}
解析注释时是往对应的绑定类中添加注入关系,绑定类通过getOrCreateTargetClass()
获得
private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap, TypeElement enclosingElement) {
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass == null) {
// 不存在的话生成,这里就是添加一个$$ViewBinder的地方
String targetType = enclosingElement.getQualifiedName().toString();
String classPackage = getPackageName(enclosingElement);
String className = getClassName(enclosingElement, classPackage) + SUFFIX;
bindingClass = new BindingClass(classPackage, className, targetType);
targetClassMap.put(enclosingElement, bindingClass);
}
return bindingClass;
}
结果就是获得了一个ViewBinder
类,并且获得了该类中需要包含的绑定关系
这个方法里提供了对特定注释的支持
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<String>();
types.add(Bind.class.getCanonicalName());
for (Class<? extends Annotation> listener : LISTENERS) {
types.add(listener.getCanonicalName());
}
types.add(BindBool.class.getCanonicalName());
types.add(BindColor.class.getCanonicalName());
types.add(BindDimen.class.getCanonicalName());
types.add(BindDrawable.class.getCanonicalName());
types.add(BindInt.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
return types;
}
生成代码
process()
里面的这句代码是生成代码的
writer.write(bindingClass.brewJava());
那么看一下brewJava()
String brewJava() {
StringBuilder builder = new StringBuilder();
builder.append("// Generated code from Butter Knife. Do not modify!\n");
builder.append("package ").append(classPackage).append(";\n\n");
if (!resourceBindings.isEmpty()) {
builder.append("import android.content.res.Resources;\n");
}
if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) {
builder.append("import android.view.View;\n");
}
builder.append("import butterknife.ButterKnife.Finder;\n");
if (parentViewBinder == null) {
builder.append("import butterknife.ButterKnife.ViewBinder;\n");
}
builder.append('\n');
builder.append("public class ").append(className);
builder.append("<T extends ").append(targetClass).append(">");
if (parentViewBinder != null) {
builder.append(" extends").append(parentViewBinder).append("<T>");
} else {
builder.append(" implements ViewBinder<T>");
}
builder.append(" {\n");
emitBindMethod(builder);
builder.append('\n');
emitUnbindMethod(builder);
builder.append("}\n");
return builder.toString();
}
可以看到生成了代码注释、包名、导包、生成类这些代码
两个花括号之间还通过emitBindMethod()
和emitUnbindMethod()
方法添加了绑定和解绑代码
下面是View
绑定的细节生成代码
private void emitViewBindings(StringBuilder builder, ViewBindings bindings) {
builder.append(" view = ");
List<ViewBinding> requiredViewBindings = bindings.getRequiredBindings();
if (requiredViewBindings.isEmpty()) {
builder.append("finder.findOptionalView(source, ").append(bindings.getId()).append(", null);\n");
} else {
if (bindings.getId() == View.NO_ID) {
builder.append("target;\n");
} else {
builder.append("finder.findRequiredView(source, ").append(bindings.getId()).append(", \"");
emitHumanDescription(builder, requiredViewBindings);
builder.append("\");\n");
}
}
emitFieldBindings(builder, bindings);
emitMethodBindings(builder, bindings);
}
我看的这个版本是拼接字符串,好像还有的版本是用
JavaPoet
生成,更加直观
最后生成的代码是类似这样的
@Override
public void bind(final Finder finder, final T target, Object source) {
super.bind(finder, target, source);
Unbinder unbinder = new Unbinder(target);
View view;
view = finder.findRequiredView(source, 2130968576, "field 'title'");
target.title = finder.castView(view, 2130968576, "field 'title'");
......
target.unbinder = unbinder;
}
APT
由于Butterknife
用到了注解处理器,所以,比起一般的框架,配置稍微多了些
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'com.jakewharton:butterknife:8.1.0'
apt 'com.jakewharton:butterknife-compiler:8.1.0'
}
可以看到用到了APT
插件,它是一个命令行工具,它对源代码文件进行检测找出其中的注解后,使用注解处理器来处理,而注解处理器使用了一套反射API
首先,APT
运行注解处理器,根据提供的源文件中的注解生成源代码文件和其它的文件,接着APT
将生成的源代码文件和提供的源文件进行编译生成类文件
APT
就像一个在编译时处理注解的javac
,实际上,APT
在Java 8
中已经被移除,它的功能已经完全集成到javac
编译器中去了
APT工作过程
从整个过程来讲,首先APT
检测在源代码文件中哪些注解存在。然后APT将查找我们编写的注解处理器工厂类,并且要求工厂类提供注解处理器以处理源文件中所涉及的注解。接下来,一个合适的注解处理器将被执行,如果在处理器生成源代码文件时,该文件中含有注解,则APT
将重复上面的过程直到没有新文件生成
编写注解处理器
处理器需要实现AnnotationProcessor
接口,这个接口有一个名为process()
的方法,该方法是在APT
调用处理器时将被用到的
一个处理器实例被其相应的工厂返回,此工厂为AnnotationProcessorFactory
接口的实现。APT
将调用工厂类的getProcessorFor()
方法来获得处理器。在调用过程中,APT
将提供给工厂类一个AnnotationProcessorEnvironment
类型的环境类对象,在这个环境对象中,将找到其执行所需要的每件东西,包括对所操作的程序结构的参考,与APT
通讯并合作一同完成新文件的建立和警告/错误信息的传输
提供工厂类有两个方式:通过APT
的-factory
命令行参数提供,或者让工厂类在APT
的发现过程中被自动定位。前者对于一个已知的工厂来讲是一种主动而又简单的方式;而后者则是需要在jar
文件的META-INF/services
目录中提供一个特定的发现路径:在包含工厂类的jar
文件中作以下的操作:在META-INF/services
目录中建立一个名为com.sun.mirror.apt.AnnotationProcessorFactory
的UTF-8编码文件,在文件中写入所有要使用到的工厂类全名,每个类为一个单独行