湖畔镇

ButterKnife框架解析

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

AbstractProcessorProcessor的子类,就是注解处理器

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,实际上,APTJava 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编码文件,在文件中写入所有要使用到的工厂类全名,每个类为一个单独行

分享