ButterKnife源碼簡析

Ari8006 7年前發布 | 11K 次閱讀 Java Android開發 移動開發 butterknife

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<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    /**
     * 主要的邏輯處理
     *
     * @param set
     * @param env
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> 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<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<String>();
        types.add(InjectView.class.getCanonicalName());
        return types;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
        generateCode(set, env);
        return true;
    }

    private void generateCode(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        StringBuilder builder = new StringBuilder()
                .append("package com.yydcdut.test.generated;\n\n")
                .append("public class GeneratedClass {\n\n") // open class
                .append("\tpublic String getMessage() {\n") // open method
                .append("\t\treturn \"");
        // 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 + "\n");
        }
        builder.append("\";\n") // end return
                .append("\t}\n") // close method
                .append("}\n"); // 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<Class<? extends Annotation>> 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<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

        annotations.add(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<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);////查找所有的被注解,封裝成BindingSet保存到map中

        //遍歷步map的生成.java文件
        for (Map.Entry<TypeElement, BindingSet> 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<TypeElement, BindingSet> ,第一個參數可以想成類,第二個參數可以想成所有有關信息的封裝;第二就是遍歷這個 map ,然后針對每個類生成對應的 ViewBinder 類。

ButterKnifeProcessor#findAndParseTargets

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

public final class ButterKnifeProcessor extends AbstractProcessor {

    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> 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<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> 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<? extends Annotation> annotationClass, String targetThing, Element element) {
        boolean hasError = false;
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

        // Verify method modifiers.
        Set<Modifier> 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<? extends Annotation> 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<? extends TypeMirror> 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<TypeElement, BindingSet.Builder> 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<ViewBinding> viewBindings;
    private final ImmutableList<FieldCollectionViewBinding> collectionBindings;
    private final ImmutableList<ResourceBinding> resourceBindings;
    private final BindingSet parentBinding;

    private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
                       boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings,
                       ImmutableList<FieldCollectionViewBinding> collectionBindings,
                       ImmutableList<ResourceBinding> 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<Id, ViewBinding.Builder> 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<ViewBinding> 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<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> 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<Map.Entry<TypeElement, BindingSet.Builder>> entries =
                new ArrayDeque<>(builderMap.entrySet());
        Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
        while (!entries.isEmpty()) {
            Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();//拿到第一個

            TypeElement type = entry.getKey();//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<TypeElement> 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<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

        for (Map.Entry<TypeElement, BindingSet> 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.\n    "
                                + "Only present for runtime invocation through {@code ButterKnife.bind()}.\n",
                        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("\n");
        }
        if (hasTargetField()) {
            constructor.addStatement("this.target = target");//賦值
            constructor.addCode("\n");
        }

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

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

        if (!resourceBindings.isEmpty()) {
            if (constructorNeedsView()) {
                constructor.addStatement("$T context = source.getContext()", CONTEXT);//賦值
            }
            if (hasResourceBindingsNeedingResource(sdk)) {
                constructor.addStatement("$T res = context.getResources()", RESOURCES);//賦值
            }
            for (ResourceBinding binding : resourceBindings) {
                constructor.addStatement("$L", binding.render(sdk));//通過不同的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<MemberViewBinding> 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<View> ALPHA_FADE = new ButterKnife.Action<View>() {
        @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<View> 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<T extends SimpleActivity> 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<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

        if (constructor == null) {
            return Unbinder.EMPTY;
        }

        //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
        try {
            return constructor.newInstance(target, source);//申明實例
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            if (cause instanceof Error) {
                throw (Error) cause;
            }
            throw new RuntimeException("Unable to create binding instance.", cause);
        }
    }

    @Nullable
    @CheckResult
    @UiThread
    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        Constructor<? extends Unbinder> 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<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);//初始化
            if (debug) {
                Log.d(TAG, "HIT: Loaded binding class and constructor.");
            }
        } catch (ClassNotFoundException e) {
            if (debug) {
                Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
            }
            bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
        }
        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/

 

 本文由用戶 Ari8006 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!