点击次数:1370      更新时间:2017-04-22 05:25:00       作者:本站整理      来源:www.lan6.net        QQ交流群:626957820


 

ButterKnife 是一个 Android 视图快速注入库,它通过给 View 字段添加注解,可以让我们丢掉 findViewById() 来获取 View 的方法,从而简化了代码。

编译时注解

概述

编译时注解的核心依赖 APT ( Annotation Processing Tools ) 实现,原理是在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查 AbstractProcessor 的子类,并且调用该类型的 process 函数,然后将添加了注解的所有元素都传递到 process 函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是EventBus,Retrofit,Dragger等开源库的基本原理。

创建

创建一个 Java Library

创建一个 annotationcompiler 的 java module

配置 gradle

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

同时需要在 app 的 module 中配置一下 Java7

compileOptions {
   sourceCompatibility JavaVersion.VERSION_1_7
   targetCompatibility JavaVersion.VERSION_1_7
}

创建 Annotation

package com.yydcdut.annotation;

public @interface InjectView {
    int value();
}

创建 AbstractProcessor

package com.yydcdut.process;

@SupportedAnnotationTypes("com.yydcdut.annotation.InjectView")
public class ViewInjectProcessor extends AbstractProcessor {
  
    /**
     * 初始化
     *
     * @param processingEnvironment 提供了一些实用的工具类Elements, Types和Filer
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }
  
    /**
     * 指定使用的java版本
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
  
    /**
     * 指定哪些注解应该被注解处理器注册
     *
     * @return
     */
    @Override
    public Set getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }
  
    /**
     * 主要的逻辑处理
     *
     * @param set
     * @param env
     * @return
     */
    @Override
    public boolean process(Set set, RoundEnvironment env) {
        return false;
    }
}

注册处理器

在 java module 里的 main 目录中创建一个 resources 文件夹,然后下边在创建 META-INF/services ,创建一个 javax.annotation.processing.Processor 文件,在此文件中写入注解处理器的类全称

com.yydcdut.process.ViewInjectProcessor

添加 android-apt

在 project 下的 build.gradle 中添加 apt 插件

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

然后在 app 中的 build.gradle 添加 apt 插件

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

添加依赖

在 app 中的 build.gradle 添加依赖

dependencies {
    compile project(':annotationcompiler')
}

Code

@SupportedAnnotationTypes("com.yydcdut.annotation.InjectView")
public class ViewInjectProcessor extends AbstractProcessor {
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new LinkedHashSet();
        types.add(InjectView.class.getCanonicalName());
        return types;
    }
  
    @Override
    public boolean process(Set set, RoundEnvironment env) {
        generateCode(set, env);
        return true;
    }
  
    private void generateCode(Set set, RoundEnvironment roundEnv) {
        StringBuilder builder = new StringBuilder()
                .append("package com.yydcdut.test.generated;

")
                .append("public class GeneratedClass {

") // open class
                .append("	public String getMessage() {
") // open method
                .append("		return "");
        // for each javax.lang.model.element.Element annotated with the CustomAnnotation
        for (Element element : roundEnv.getElementsAnnotatedWith(InjectView.class)) {
            int id = element.getAnnotation(InjectView.class).value();
            builder.append(id + "
");
        }
        builder.append("";
") // end return
                .append("	}
") // close method
                .append("}
"); // close class


        try { // write the file
            JavaFileObject source = processingEnv.getFiler().createSourceFile("com.yydcdut.test.generated.GeneratedClass");


            Writer writer = source.openWriter();
            writer.write(builder.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            // Note: calling e.printStackTrace() will print IO errors
            // that occur from the file already existing after its first run, this is normal
            e.printStackTrace();
        }
    }
}

被注解的部分代码:

package com.yydcdut.textdemo;

public class MainActivity extends Activity {
    @InjectView(value = 123)
    private EditText mEditText;
}

编译之后查看 app/build/generated/source/apt/debug/com/yydcdut/test/generated/GeneratedClass.java

源码解析

ButterKnifeProcessor

这里只分析 BindView 的过程

ButterKnifeProcessor

根据上述创建注解器的流程,那么我们分析源码也有了一个流程,就直接看 ButterKnife 的 AbstractProcessor :

@AutoService(Processor.class)//自动注册处理器
public final class ButterKnifeProcessor extends AbstractProcessor {
  
    private static final List> LISTENERS = Arrays.asList(//
            OnCheckedChanged.class, //
            OnClick.class, //
            OnEditorAction.class, //
            OnFocusChange.class, //
            OnItemClick.class, //
            OnItemLongClick.class, //
            OnItemSelected.class, //
            OnLongClick.class, //
            OnPageChange.class, //
            OnTextChanged.class, //
            OnTouch.class //
    );
  
    private Elements elementUtils;//处理Element的工具类
    private Types typeUtils;//处理TypeMirror的工具类
    private Filer filer;//可以创建.java文件
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        //......
        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
        //......
    }
  
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new LinkedHashSet<>();
        for (Class annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    private Set> getSupportedAnnotations() {
        Set> annotations = new LinkedHashSet<>();

        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(BindInt.class);
        annotations.add(BindString.class);
        annotations.add(BindView.class);
        annotations.add(BindViews.class);
        annotations.addAll(LISTENERS);

        return annotations;
    }
  
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
  
    @Override
    public boolean process(Set elements, RoundEnvironment env) {
        Map bindingMap = findAndParseTargets(env);////查找所有的被注解,封装成BindingSet保存到map中

        //遍历步map的生成.java文件
        for (Map.Entry entry : bindingMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            BindingSet binding = entry.getValue();

            JavaFile javaFile = binding.brewJava(sdk);
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
            }
        }

        return false;
    }
}

在 process 中主要做了两件事,第一是找出被注解的元素,封装成 Map ,第一个参数可以想成类,第二个参数可以想成所有有关信息的封装;第二就是遍历这个 map ,然后针对每个类生成对应的 ViewBinder 类。

ButterKnifeProcessor#findAndParseTargets

那么主要的查找工作在 findAndParseTargets() 中进行:

public final class ButterKnifeProcessor extends AbstractProcessor {

    private Map findAndParseTargets(RoundEnvironment env) {
        Map builderMap = new LinkedHashMap<>();
        Set erasedTargetNames = new LinkedHashSet<>();
        // .......
       
        // Process each @BindView element.
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {//拿到被BindView注解的元素
            // 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);
            }
        }
        // .......
      
        return bindingMap;
    }
}

RoundEnvironment 可以理解成查询注解信息的类,而 Element 可以理解成程序中的元素,比如包、类、方法等等。

public class ClassA { // TypeElement
    private int var; // VariableElement

    public ClassA() {// ExecuteableElement
    }

    public void setA(int newA // TypeElement
    ) {
    }
}

TypeElement 代表源代码中元素类型,但是 TypeElement 并不包含类的相关信息,可以从 TypeElement 获取类的名称,但不能获取类的信息,比如说父类。这些信息可以通过 TypeMirror 获取。你可以通过调用 element.asType() 来获取一个 Element 的 TypeMirror 。

ButterKnifeProcessor#parseBindView

public final class ButterKnifeProcessor extends AbstractProcessor {
    private void parseBindView(Element element, Map builderMap, Set erasedTargetNames) {
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();//类 com.yydcdut.textdemo.MainActivity

        // Start by verifying common generated code restrictions.
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
                || isBindingInWrongPackage(BindView.class, element);//检查合法性

        // Verify that the target type extends from View.
        TypeMirror elementType = element.asType();//android.widget.EditText
        if (elementType.getKind() == TypeKind.TYPEVAR) {//false (elementType.getKind()==DECLARED)
            TypeVariable typeVariable = (TypeVariable) elementType;
            elementType = typeVariable.getUpperBound();
        }
        Name qualifiedName = enclosingElement.getQualifiedName();//com.yydcdut.textdemo.MainActivity
        Name simpleName = element.getSimpleName();//mEditText
        if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {//必须为view类型的子类或者是接口
            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;
        }

        // Assemble information on the field.
        int id = element.getAnnotation(BindView.class).value();//得到id

        BindingSet.Builder builder = builderMap.get(enclosingElement);
        QualifiedId qualifiedId = elementToQualifiedId(element, id);//封装成QualifiedId
        if (builder != null) {
            String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
            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 = getOrCreateBindingBuilder(builderMap, enclosingElement);//为这个类创建BindingSet.Builder
        }

        String name = simpleName.toString();//mEditText
        TypeName type = TypeName.get(elementType);//android.widget.EditText
        boolean required = isFieldRequired(element);

        builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));//封装后放入builder中

        // Add the type-erased version to the valid binding targets set.
        erasedTargetNames.add(enclosingElement);
    }
  
    /**
     * 判断修饰符等
     *
     * @param annotationClass
     * @param targetThing
     * @param element
     * @return
     */
    private boolean isInaccessibleViaGeneratedCode(Class annotationClass, String targetThing, Element element) {
        boolean hasError = false;
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

        // Verify method modifiers.
        Set modifiers = element.getModifiers();
        if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {//不能是private或者static的
            error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
            hasError = true;
        }

        // Verify containing type.
        if (enclosingElement.getKind() != CLASS) {//不能是class
            error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
            hasError = true;
        }

        // Verify containing class visibility is not private.
        if (enclosingElement.getModifiers().contains(PRIVATE)) {//不能是private
            error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
            hasError = true;
        }

        return hasError;
    }

    /**
     * 判断是否弄错了包名
     * 弄成android或者java的
     *
     * @param annotationClass
     * @param element
     * @return
     */
    private boolean isBindingInWrongPackage(Class annotationClass, Element element) {
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
        String qualifiedName = enclosingElement.getQualifiedName().toString();//com.yydcdut.textdemo.MainActivity

        if (qualifiedName.startsWith("android.")) {
            error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
                    annotationClass.getSimpleName(), qualifiedName);
            return true;
        }
        if (qualifiedName.startsWith("java.")) {
            error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
                    annotationClass.getSimpleName(), qualifiedName);
            return true;
        }

        return false;
    }
  
    /**
     * 是不是otherType的子类型
     *
     * @param typeMirror
     * @param otherType
     * @return
     */
    static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
        if (isTypeEqual(typeMirror, otherType)) {
            return true;
        }
        if (typeMirror.getKind() != TypeKind.DECLARED) {
            return false;
        }
        DeclaredType declaredType = (DeclaredType) typeMirror;
        List typeArguments = declaredType.getTypeArguments();
        if (typeArguments.size() > 0) {
            StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
            typeString.append('<');
            for (int i = 0; i < typeArguments.size(); i++) {
                if (i > 0) {
                    typeString.append(',');
                }
                typeString.append('?');
            }
            typeString.append('>');
            if (typeString.toString().equals(otherType)) {
                return true;
            }
        }
        Element element = declaredType.asElement();
        if (!(element instanceof TypeElement)) {
            return false;
        }
        TypeElement typeElement = (TypeElement) element;
        TypeMirror superType = typeElement.getSuperclass();
        if (isSubtypeOfType(superType, otherType)) {
            return true;
        }
        for (TypeMirror interfaceType : typeElement.getInterfaces()) {
            if (isSubtypeOfType(interfaceType, otherType)) {
                return true;
            }
        }
        return false;
    }
  
    /**
     * 是不是接口
     *
     * @param typeMirror
     * @return
     */
    private boolean isInterface(TypeMirror typeMirror) {
        return typeMirror instanceof DeclaredType
                && ((DeclaredType) typeMirror).asElement().getKind() == INTERFACE;
    }
  
    private QualifiedId elementToQualifiedId(Element element, int id) {
        return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);
    }
  
    /**
     * 判断有没有被Nullable修饰
     *
     * @param element
     * @return
     */
    private static boolean isFieldRequired(Element element) {
        return !hasAnnotationWithName(element, NULLABLE_ANNOTATION_NAME);
    }

    private BindingSet.Builder getOrCreateBindingBuilder(
            Map builderMap, TypeElement enclosingElement) {
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        if (builder == null) {
            builder = BindingSet.newBuilder(enclosingElement);
            builderMap.put(enclosingElement, builder);
        }
        return builder;
    }
}

这里是解析被 BindView 修饰的元素,将信息都封装成 BindingSet.Builder 。

BindingSet#newBuilder

final class BindingSet {
  
    private final TypeName targetTypeName;
    private final ClassName bindingClassName;
    private final boolean isFinal;
    private final boolean isView;
    private final boolean isActivity;
    private final boolean isDialog;
    private final ImmutableList viewBindings;
    private final ImmutableList collectionBindings;
    private final ImmutableList resourceBindings;
    private final BindingSet parentBinding;

    private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
                       boolean isView, boolean isActivity, boolean isDialog, ImmutableList viewBindings,
                       ImmutableList collectionBindings,
                       ImmutableList resourceBindings, BindingSet parentBinding) {
        this.isFinal = isFinal;
        this.targetTypeName = targetTypeName;
        this.bindingClassName = bindingClassName;
        this.isView = isView;
        this.isActivity = isActivity;
        this.isDialog = isDialog;
        this.viewBindings = viewBindings;
        this.collectionBindings = collectionBindings;
        this.resourceBindings = resourceBindings;
        this.parentBinding = parentBinding;
    }

    static Builder newBuilder(TypeElement enclosingElement) {
        TypeMirror typeMirror = enclosingElement.asType();//android.widget.EditText

        boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);//是不是View
        boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);//是不是Activity
        boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);//是不是Dialog

        TypeName targetType = TypeName.get(typeMirror);
        if (targetType instanceof ParameterizedTypeName) {
            targetType = ((ParameterizedTypeName) targetType).rawType;
        }

        String packageName = getPackage(enclosingElement).getQualifiedName().toString();//com.yydcdut.textdemo
        String className = enclosingElement.getQualifiedName().toString().substring(
                packageName.length() + 1).replace('.', '$');//MainActivity
        ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");//com.yydcdut.textdemo.MainActivity_ViewBinding

        boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);//是不是final修饰的
        return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
    }
  
    static final class Builder {
      
        private final TypeName targetTypeName;
        private final ClassName bindingClassName;
        private final boolean isFinal;
        private final boolean isView;
        private final boolean isActivity;
        private final boolean isDialog;
      
        private final Map viewIdMap = new LinkedHashMap<>();

        private Builder(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
                        boolean isView, boolean isActivity, boolean isDialog) {
            this.targetTypeName = targetTypeName;
            this.bindingClassName = bindingClassName;
            this.isFinal = isFinal;
            this.isView = isView;
            this.isActivity = isActivity;
            this.isDialog = isDialog;
        }
      
        void addField(Id id, FieldViewBinding binding) {
            getOrCreateViewBindings(id).setFieldBinding(binding);
        }
      
        private ViewBinding.Builder getOrCreateViewBindings(Id id) {
            ViewBinding.Builder viewId = viewIdMap.get(id);
            if (viewId == null) {
                viewId = new ViewBinding.Builder(id);
                viewIdMap.put(id, viewId);
            }
            return viewId;
        }
      
        BindingSet build() {
            ImmutableList.Builder viewBindings = ImmutableList.builder();
            for (ViewBinding.Builder builder : viewIdMap.values()) {
                viewBindings.add(builder.build());
            }
            return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog, viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding);
        }
    }
}

ButterKnifeProcessor#findAndParseTargets

public final class ButterKnifeProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set elements, RoundEnvironment env) {
        Map builderMap = new LinkedHashMap<>();
        //....
      
        //....
        // Associate superclass binders with their subclass binders. This is a queue-based tree walk
        // which starts at the roots (superclasses) and walks to the leafs (subclasses).
        Deque> entries =
                new ArrayDeque<>(builderMap.entrySet());
        Map bindingMap = new LinkedHashMap<>();
        while (!entries.isEmpty()) {
            Map.Entry entry = entries.removeFirst();//拿到第一个

            TypeElement type = entry.getKey();//com.yydcdut.textdemo.MainActivity
            BindingSet.Builder builder = entry.getValue();

            TypeElement parentType = findParentType(type, erasedTargetNames);//父类
            if (parentType == null) {//自己就是了
                bindingMap.put(type, builder.build());
            } else {
                BindingSet parentBinding = bindingMap.get(parentType);//拿到父类的BindingSet
                if (parentBinding != null) {
                    builder.setParent(parentBinding);
                    bindingMap.put(type, builder.build());
                } else {
                    // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
                    entries.addLast(entry);//放到最后(先解析父类的)
                }
            }
        }

        return bindingMap;
    }
  
   /**
     * Finds the parent binder type in the supplied set, if any.
     */
    private TypeElement findParentType(TypeElement typeElement, Set parents) {
        TypeMirror type;
        while (true) {
            type = typeElement.getSuperclass();
            if (type.getKind() == TypeKind.NONE) {
                return null;
            }
            typeElement = (TypeElement) ((DeclaredType) type).asElement();
            if (parents.contains(typeElement)) {
                return typeElement;
            }
        }
    }
}

ButterKnifeProcessor#process

findAndParseTargets 算是解析完了,再回过头来看第二步,生成文件的步骤

public final class ButterKnifeProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set elements, RoundEnvironment env) {
        Map bindingMap = findAndParseTargets(env);

        for (Map.Entry entry : bindingMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            BindingSet binding = entry.getValue();

            JavaFile javaFile = binding.brewJava(sdk);//变成JavaFile
            try {
                javaFile.writeTo(filer);//生成文件
            } catch (IOException e) {
                error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
            }
        }

        return false;
    }
}

将信息转变成 JavaFile ,通过 filer 完成。

write过程

final class BindingSet {
    JavaFile brewJava(int sdk) {
        return JavaFile.builder(bindingClassName.packageName(), createType(sdk))//packageName-->com.yydcdut.textdemo
                .addFileComment("Generated code from Butter Knife. Do not modify!")
                .build();
    }
  
    private TypeSpec createType(int sdk) {
        TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
                .addModifiers(PUBLIC);//申明类
        if (isFinal) {
            result.addModifiers(FINAL);//添加final关键字
        }

        if (parentBinding != null) {
            result.superclass(parentBinding.bindingClassName);//继承父类
        } else {
            result.addSuperinterface(UNBINDER);//实现Unbinder接口
        }

        if (hasTargetField()) {
            result.addField(targetTypeName, "target", PRIVATE);//设置private的target变量
        }

        if (isView) {
            result.addMethod(createBindingConstructorForView());
        } else if (isActivity) {
            result.addMethod(createBindingConstructorForActivity());
        } else if (isDialog) {
            result.addMethod(createBindingConstructorForDialog());
        }
        if (!constructorNeedsView()) {
            // Add a delegating constructor with a target type + view signature for reflective use.
            result.addMethod(createBindingViewDelegateConstructor());
        }
        result.addMethod(createBindingConstructor(sdk));

        if (hasViewBindings() || parentBinding == null) {
            result.addMethod(createBindingUnbindMethod(result));
        }

        return result.build();
    }
  
    private MethodSpec createBindingConstructorForView() {
        MethodSpec.Builder builder = MethodSpec.constructorBuilder()//创建构造器
                .addAnnotation(UI_THREAD)//添加UIThread的注解
                .addModifiers(PUBLIC)//为public
                .addParameter(targetTypeName, "target");//构造函数中得有target参数
        if (constructorNeedsView()) {//构造函数中的方法
            builder.addStatement("this(target, target)");
        } else {
            builder.addStatement("this(target, target.getContext())");
        }
        return builder.build();
    }

    /**
     * True if this binding requires a view. Otherwise only a context is needed.
     */
    private boolean constructorNeedsView() {
        return hasViewBindings() //
                || parentBinding != null && parentBinding.constructorNeedsView();
    }
  
    private MethodSpec createBindingViewDelegateConstructor() {
        return MethodSpec.constructorBuilder()
                .addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.
    "
                                + "Only present for runtime invocation through {@code ButterKnife.bind()}.
",
                        bindingClassName, targetTypeName, CONTEXT)
                .addAnnotation(Deprecated.class)
                .addAnnotation(UI_THREAD)
                .addModifiers(PUBLIC)
                .addParameter(targetTypeName, "target")
                .addParameter(VIEW, "source")
                .addStatement(("this(target, source.getContext())"))
                .build();
    }
  
    private MethodSpec createBindingConstructor(int sdk) {
        MethodSpec.Builder constructor = MethodSpec.constructorBuilder()//构造方法
                .addAnnotation(UI_THREAD)//UIThread注解
                .addModifiers(PUBLIC);//public

        if (hasMethodBindings()) {
            constructor.addParameter(targetTypeName, "target", FINAL);//final的target变量
        } else {
            constructor.addParameter(targetTypeName, "target");
        }

        if (constructorNeedsView()) {
            constructor.addParameter(VIEW, "source");//变量名为source的View
        } else {
            constructor.addParameter(CONTEXT, "context");//变量名为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());
        }

        if (hasOnTouchMethodBindings()) {
            constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
                    .addMember("value", "$S", "ClickableViewAccessibility")
                    .build());
        }

        if (parentBinding != null) {//如果有父类,则调用super
            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("
");
        }
        if (hasTargetField()) {
            constructor.addStatement("this.target = target");//赋值
            constructor.addCode("
");
        }

        if (hasViewBindings()) {
            if (hasViewLocal()) {
                // Local variable in which all views will be temporarily stored.
                constructor.addStatement("$T view", VIEW);
            }
            for (ViewBinding binding : viewBindings) {
                addViewBinding(constructor, binding);//View的初始化
            }
            for (FieldCollectionViewBinding binding : collectionBindings) {
                constructor.addStatement("$L", binding.render());//资源的初始化
            }

            if (!resourceBindings.isEmpty()) {
                constructor.addCode("
");
            }
        }

        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));//通过不同的SDK版本实现Resource相关(Drawable,Color等)的初始化
            }
        }

        return constructor.build();
    }
  
    private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
        if (binding.isSingleFieldBinding()) {//只有findView的情况,没有监听器
            // Optimize the common case where there's a single binding directly to a field.
            FieldViewBinding fieldBinding = binding.getFieldBinding();
            CodeBlock.Builder builder = CodeBlock.builder()
                    .add("target.$L = ", fieldBinding.getName());//(target-->MainActivity) ($L-->mEditText)

            boolean requiresCast = requiresCast(fieldBinding.getType());//是否需要强转
            if (!requiresCast && !fieldBinding.isRequired()) {
                builder.add("source.findViewById($L)", binding.getId().code);//findViewById
            } 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(")");//target.mEditText = Utils.findRequiredViewAsType(source, R.id.edit, "field 'mEditText'", EditText.class);
            }
            result.addStatement("$L", builder.build());
            return;
        }

        List requiredBindings = binding.getRequiredBindings();
        if (requiredBindings.isEmpty()) {
            result.addStatement("view = source.findViewById($L)", binding.getId().code);
        } else if (!binding.isBoundToRoot()) {
            result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
                    binding.getId().code, asHumanDescription(requiredBindings));//view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
        }

        addFieldBinding(result, binding);//强转
        addMethodBindings(result, binding);//各种监听器
    }

    private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding) {
        FieldViewBinding fieldBinding = binding.getFieldBinding();
        if (fieldBinding != null) {
            if (requiresCast(fieldBinding.getType())) {
                result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)",
                        fieldBinding.getName(), UTILS, binding.getId().code,
                        asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType());// target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
            } else {
                result.addStatement("target.$L = view", fieldBinding.getName());
            }
        }
    }
}

最后文件的生成是通过 com.squareup.javapoet.JavaFile 生成的。

官方 demo 的栗子

package com.example.butterknife.library;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

import butterknife.BindView;
import butterknife.BindViews;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnItemClick;
import butterknife.OnLongClick;

import static android.widget.Toast.LENGTH_SHORT;

public class SimpleActivity extends Activity {
    private static final ButterKnife.Action ALPHA_FADE = new ButterKnife.Action() {
        @Override
        public void apply(@NonNull View view, int index) {
            AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
            alphaAnimation.setFillBefore(true);
            alphaAnimation.setDuration(500);
            alphaAnimation.setStartOffset(index * 100);
            view.startAnimation(alphaAnimation);
        }
    };

    @BindView(R2.id.title)
    TextView title;
    @BindView(R2.id.subtitle)
    TextView subtitle;
    @BindView(R2.id.hello)
    Button hello;
    @BindView(R2.id.list_of_things)
    ListView listOfThings;
    @BindView(R2.id.footer)
    TextView footer;

    @BindViews({R2.id.title, R2.id.subtitle, R2.id.hello})
    List headerViews;

    private SimpleAdapter adapter;

    @OnClick(R2.id.hello)
    void sayHello() {
        Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
        ButterKnife.apply(headerViews, ALPHA_FADE);
    }

    @OnLongClick(R2.id.hello)
    boolean sayGetOffMe() {
        Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show();
        return true;
    }

    @OnItemClick(R2.id.list_of_things)
    void onItemClick(int position) {
        Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.simple_activity);
        ButterKnife.bind(this);

        // Contrived code to use the bound fields.
        title.setText("Butter Knife");
        subtitle.setText("Field and method binding for Android views.");
        footer.setText("by Jake Wharton");
        hello.setText("Say Hello");

        adapter = new SimpleAdapter(this);
        listOfThings.setAdapter(adapter);
    }
}

官方 demo 的 apt 生成类的栗子

// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;

import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;

public class SimpleActivity_ViewBinding implements Unbinder {
  protected T target;

  private View view2130968578;

  private View view2130968579;

  @UiThread
  public SimpleActivity_ViewBinding(final T target, View source) {
    this.target = target;

    View view;
    target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
    view2130968578 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
    view2130968579 = view;
    ((AdapterView) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
  }

  @Override
  @CallSuper
  public void unbind() {
    T target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.title = null;
    target.subtitle = null;
    target.hello = null;
    target.listOfThings = null;
    target.footer = null;
    target.headerViews = null;

    view2130968578.setOnClickListener(null);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;

    this.target = null;
  }
}

ButterKnife

当要进行 bind 的时候,都需要在 Activity 或 View 等的初始化函数中进行绑定,这里就拿 Activity 为栗子进行分析:

public final class ButterKnife {
    /**
     * BindView annotated fields and methods in the specified {@link Activity}. The current content
     * view is used as the view root.
     *
     * @param target Target activity for view binding.
     */
    @NonNull
    @UiThread
    public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return createBinding(target, sourceView);
    }
 
    private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
        Class targetClass = target.getClass();
        if (debug) {
            Log.d(TAG, "Looking up binding for " + targetClass.getName());
        }
        Constructor 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);
        }
    }
  
    @Nullable
    @CheckResult
    @UiThread
    private static Constructor findBindingConstructorForClass(Class cls) {
        Constructor bindingCtor = BINDINGS.get(cls);//判断绑定过了没有
        if (bindingCtor != null) {
            if (debug) {
                Log.d(TAG, "HIT: Cached in binding map.");
            }
            return bindingCtor;
        }
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")) {//排除android和java的包
            if (debug) {
                Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
            }
            return null;
        }
        try {
            Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");//加载后面加"_ViewBinding"的累
            //noinspection unchecked
            bindingCtor = (Constructor) 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);
        }
        BINDINGS.put(cls, bindingCtor);
        return bindingCtor;
    }
}

最终通过 ClassLoader 的方式将类加载出来,最后 constructor.newInstance 方法来调用该类的构造函数。

总结

平时了解到更多的是运行时注解,即声明注解的生命周期为 RUNTIME ,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是设计到比较多的反射,必然多多少少会有性能的损耗。而 ButterKnife 用的 APT 编译时解析技术,比较好的解决了反射这些问题。

APT 大概就是声明的注解的生命周期为 CLASS ,然后继承 AbstractProcessor 类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用 AbstractProcessor 的 process 方法,对注解进行处理,那么就可以在处理的时候,动态生成绑定事件或者控件的 java 代码,然后在运行的时候,直接调用 bind 方法完成绑定。

其实这种方式的好处是我们不用再一遍一遍地写 findViewById 和 onClick 等代码了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。

来自:http://yydcdut.com/2017/04/19/butterknife-analyse/

<