美文网首页
Butterknife深入理解之自己动手编写Butterknif

Butterknife深入理解之自己动手编写Butterknif

作者: JimmieYang | 来源:发表于2018-11-29 18:29 被阅读2319次

    Demo的源码地址在 mini-butterknife, 有兴趣的可以下载源码来看.

    Butterknife 框架介绍

    butterknife是一款View注入的框架,在android开发时省去我们重复的敲打findViewById,setOnclickListener等方法.

    Field and method binding for Android views which uses annotation processing to generate boilerplate code for you.
    通过注解处理器 (Annotation Processor) 自动生成样板代码,来绑定 Android视图中的 字段 和方法.

    从方法的介绍中,我们可以看出, Butterknife 使用 APT(Annotation Processing Tool)编译时解析技术来实现代码的生成。

    (注:我们也可以在 运行时, 通过反射的方式拿到 注解信息进行处理, 但是这种方式效率比较低,不是本文讨论的问题.)

    而代码的生成, Butterknife 则使用 JavaPoet 库来生成java文件.

    APT 是需要你 继承 AbstractProcessor类, 在编译期的时候会运行 AbstractProcessorprocess方法来解析注解, 以及处理生成样板代码.

    而样板代码,则通过 反射调用来进行.

    所以, 可以总结成三个步骤

    1. 继承AbstractProcessor解析注解数据
    2. 根据解析的注解数据,通过JavaPoet生成java样板代码
    3. 运行时,通过反射获取到样板代码的实例,进行调用

    注解知识

    网上有很多java注解相关的文章, 这里就不对注解的基础知识进行展开,推荐 Java注解处理器 进行阅读.

    这里主要对自定义注解的解析做一些说明.

    AbstractProcessor

    需要关注注解处理器的以下方法 :

    • void init(ProcessingEnvironment processingEnv)

    init方法中,主要是为了 获取以下 辅助的方法类.

    env.getFiler() : 注解的文件处理类
    env.getMessager() : 日志输出类
    env.getElementUtils() : 元素辅助类
    env.getTypeUtils() : 类型辅助类等.

    • Set<String> getSupportedAnnotationTypes()

    这里需要你指定 你关注的注解. 或者说 你关注的注解类型,需要在这里注册,process方法才能够正常处理你关注的注解.

    • SourceVersion getSupportedSourceVersion()

    指定你使用的Java版本。通常这里返回SourceVersion.latestSupported().

    • boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)

    这里是你的扫描、评估和处理注解,以及生成Java文件的逻辑代码.

    模板代码:

    // 使用@AutoService注解,避免了在resource文件夹中注册的步骤
    @AutoService(Processor.class)
    public class MyProcessor extends AbstractProcessor {
        private Filer filer;
        private Messager messager;
    
        /**
         * 初始化, 获取各种工具类
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            filer = processingEnv.getFiler();
            messager = processingEnv.getMessager();
        }
    
        /**
         * 注册感兴趣的注解类
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> annotations = new LinkedHashSet<>();
            annotations.add(BindView.class.getCanonicalName());
            annotations.add(OnClick.class.getCanonicalName());
            return annotations;
        }
    
        /**
         * 返回使用的java版本
         */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        /**
         * 扫描、评估和处理注解,以及生成Java文件的逻辑代码
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            return handleProcess(annotations, roundEnv);
        }
    }
    

    MODEL API

    注解元素解析相关的api,称其为MODEL API是因为 注解解析的类存放在 javax.lang.model包下.

    process方法中,我们通过 for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) 来遍历我们感兴趣的注解元素.

    1. 判断注解是否使用在字段上
        // 判断注释 是否在 字段上
        if (!(element instanceof VariableElement) ||  element.getKind() != ElementKind.FIELD) {
            throw new IllegalStateException("@BindView annotation must be on a field.");
        }
    
    1. 判断注解是否使用在方法上
                // 判断注释 是否在 方法上
                if (!(element instanceof ExecutableElement) || element.getKind() != ElementKind.METHOD) {
                    throw new IllegalStateException("@OnClick annotation must be on a method.");
                }
    
    1. 获取被注解所在的类的名称
                // 获取目标类的类型
                // 如(xxx.MainActivity)
                TypeElement typeElement = (TypeElement) element.getEnclosingElement();
                String targetClassName = typeElement.getQualifiedName().toString();
    
    1. 获取注解实例,以及注解中的值
    // 获取注解实例
    BindView bindView = element.getAnnotation(BindView.class);
    // 获取注解值
    // @BindView(R.id.btn) 中 R.id.btn对应的int值
    int value = bindView.value();
    
    1. 获取字段或者方法的名称
    // 获取字段或者方法的名称
    // Button btn; 中的 btn
    // void handleClick(View v); 中的 handleClick
    String name = element.getSimpleName().toString();
    
    1. 获取字段或者方法中的参数或者返回值的类型

    由于原生中,确定类型的判断方法比较繁琐(需要根据各种getKind判断基础类型,引用类型,数组类型,泛型等),这里可以直接通过 JavaPoet库中的 TypeName类来确定类型

            // 可以根据 javapoet 中的 TypeName 来确定类型
            // @BindView(R.id.btn) Button btn; 中 得到 android.view.Button
            TypeMirror mirror = element.asType();
            TypeName typeName = TypeName.get(mirror);
    
    1. 获取方法的返回类型
            // 获取返回值的类型
            ExecutableElement executableElement = (ExecutableElement) element;
            TypeMirror returnType = executableElement.getReturnType();
            TypeName returnClass = TypeName.get(returnType);
    
    1. 获取方法的参数信息
            ExecutableElement executableElement = (ExecutableElement) element;
            // 获取方法的参数信息
            List<? extends VariableElement> parameters = executableElement.getParameters();
            // 获取传入参数的个数
            int parameterSize = parameters.size();
            // 获取参数类型以及参数名称
            for (VariableElement parameter : parameters) {
                TypeMirror paramType = parameter.asType();
                // 参数类型
                TypeName paramClass = TypeName.get(paramType);
                // 参数名
                Name paramName = parameter.getSimpleName();
            }
    
    1. 判断是否 该类型是否是某个类型的子类型
        /**
         * 递归向上查找 是否是其对应的子类型
         */
        static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
            if (otherType.equals(typeMirror.toString())) {
                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;
        }
    

    JavaPoet

    JavaPoet 是一个使用可编程的方式来生成java源文件.

    GitHub地址 : JavaPoet

    readme 里面详细的介绍了 它的用法,这里就不再赘述.

    动手编写ButterKnife

    一个示例

    public class MainActivity extends AppCompatActivity {
        // 解绑器
        private Unbinder unbinder;
    
        @BindView(R.id.btn2)
        Button btn2;
        @BindView(R.id.btn)
        Button button;
    
        int id = 0;
    
        @OnClick({R.id.btn, R.id.btn2})
        void clickMethod(View view) {
            Toast.makeText(this, "i am a toast !!", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            FloatingActionButton fab = findViewById(R.id.fab);
            fab.setOnClickListener(view -> {
                button.setText("i'm button " + (++id));
                btn2.setText("xxx button " + (++id));
            });
            // 绑定
            unbinder = ButterKnife.bind(this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 解绑, 移除监听器等
            if (unbinder != null) unbinder.unbind();
        }
    }
    

    这里再说一下 ButterKnife的流程.

    1. 通过 注解处理器 解析注解数据, 并且生成 java源代码
    public class MainActivity_ViewBinding implements Unbinder {
      private MainActivity target;
    
      private View view7f080025;
    
      private View view7f080024;
    
      @UiThread
      public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    
      @UiThread
      public MainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
    
        View view;
        view = Utils.findRequiredView(source, R.id.btn2, "field 'btn2' and method 'clickMethod'");
        target.btn2 = Utils.castView(view, R.id.btn2, "field 'btn2'", Button.class);
        view7f080025 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.clickMethod(p0);
          }
        });
        view = Utils.findRequiredView(source, R.id.btn, "field 'button' and method 'clickMethod'");
        target.button = Utils.castView(view, R.id.btn, "field 'button'", Button.class);
        view7f080024 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.clickMethod(p0);
          }
        });
      }
    
      @Override
      @CallSuper
      public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
    
        target.btn2 = null;
        target.button = null;
    
        view7f080025.setOnClickListener(null);
        view7f080025 = null;
        view7f080024.setOnClickListener(null);
        view7f080024 = null;
      }
    }
    

    这个就是 ButterKnife 编译时生成代码.

    1. ButterKnife.bind(this);时, 方法内部使用反射生成上述样板代码的实例返回.

    也就是我们拿到的 Unbinder实例对象.

    有了上面的介绍,我们可以很轻松的写出上述的代码功能.

    模块划分

    我们将 功能分成3个模块,来构建项目.

    1. 注解模块(butterknife_annotations)

    注解模块, 存放着所有的注解类,之所以将它 作为一个单独的模块存放,是因为 注解类在注解处理器阶段(即编译阶段)需要使用到, 并且在运行阶段(第三方使用过程中) 也需要用到, 这种两个阶段都需要用到的我们单独分一个模块.

    1. 注解处理器模块(butterknife_processor)

    注解处理器模块,使用来 获取注解信息和生成java样板代码的. 只在编译器使用, 代码不会被打包到程序中去.

    gradle依赖中使用 annotationProcessor project(':butterknife_processor')来导入.

    1. butterknife模块

    这个模块负责绑定 activity等类, 通过反射获取 样板代码的实例,返回给调用者,这个模块是 运行时依赖.

    注解模块(butterknife_annotations)

    文章的目的是为了 实现 @BindView@OnClick注解的功能.

    因为, 这个模块,只有这两个类的代码.

    // OnClick.java
    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.METHOD)
    public @interface OnClick {
        @IdRes int[] value();
    }
    
    // BindView.java
    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.FIELD)
    public @interface BindView {
        @IdRes int value();
    }
    

    这里由于不考虑,运行时通过反射拿到注解, 所以直接使用 RetentionPolicy.SOURCE策略.

    注解处理器模块(butterknife_processor)

    这个模块是生成样板代码的模块, 我们需要每一个 目标类(如MainActivity)中的所有注解, 解析后生成一份样板代码.

        @BindView(R.id.btn2)
        Button btn2;
    

    @BindView 是注解在 字段上, 我们需要关注它 字段名, 和字段类型, 以及注解的值(R.id.btn2)

    public class FieldBinding {
        private int value;
        private ClassName fieldType;
        private String fieldName;
    
        public FieldBinding(int value, ClassName fieldType, String name) {
            this.value = value;
            this.fieldType = fieldType;
            this.fieldName = name;
        }
    
        public int getValue() {
            return value;
        }
    
        public ClassName getFieldType() {
            return fieldType;
        }
    
        public String getFieldName() {
            return fieldName;
        }
    }
    
        @OnClick({R.id.btn, R.id.btn2})
        void click(View view) {
            Toast.makeText(this, "i am a toast !!", Toast.LENGTH_SHORT).show();
        }
    

    @OnClick 是注解在方法上的, 我们需要关注它的 方法名,和注解的值; 由于 @OnClick 注解的方法,如果有方法参数,必定只能是 android.view.View,所以这里只判断是否有参数就行了.

    public class MethodBinding {
        private String methodName;
        private int value;
        private boolean hasParam;
    
        public MethodBinding(String methodName, int value, boolean hasParam) {
            this.methodName = methodName;
            this.hasParam = hasParam;
            this.value = value;
        }
    
        public String getMethodName() {
            return methodName;
        }
    
        public int getValue() {
            return value;
        }
    
        public boolean hasParam() {
            return hasParam;
        }
    }
    

    我们需要根据我们关注的这些数据, 使用 javapoet 库来生成样板代码, 这里直接贴出, 生成代码的类, api的使用 进入 javapoet 官网自行查找.

    class ViewBinding {
        private static final ClassName UTILS = ClassName.get("cn.jimmie.learn.butterknife", "Utils");
        private static final ClassName VIEW = ClassName.get("android.view", "View");
        private static final ClassName UNBINDER = ClassName.get("cn.jimmie.learn.butterknife", "Unbinder");
        private static final ClassName LISTENER = ClassName.get("cn.jimmie.learn.butterknife", "DebouncingOnClickListener");
        private static final ClassName ILLEGAL_STATE_EXCEPTION = ClassName.get("java.lang", "IllegalStateException");
    
        private List<FieldBinding> fieldBindings = new ArrayList<>();
        private List<MethodBinding> methodBindings = new ArrayList<>();
        private ClassName target;
    
        ViewBinding(ClassName target) {
            this.target = target;
        }
    
        void addFiled(FieldBinding field) {
            fieldBindings.add(field);
        }
    
        void addMethod(MethodBinding method) {
            methodBindings.add(method);
        }
    
    
        JavaFile brewJava() {
            // 单参数构造函数 $ctor(Object target)
            MethodSpec ctor1 = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(target, "target")
                    .addStatement("this(target, target.getWindow().getDecorView())")
                    .build();
    
            // 构建两个参数的构造函数 $ctor(Object target,View source)
            MethodSpec.Builder ctorBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(target, "target", Modifier.FINAL)
                    .addParameter(VIEW, "source")
                    .addStatement("this.$N = $N", "target", "target");
    
            // 解绑器
            MethodSpec.Builder unbind = MethodSpec.methodBuilder("unbind")
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .addStatement("if (target == null) throw new $T(\"Bindings already cleared.\")", ILLEGAL_STATE_EXCEPTION);
    
            // 类构建器
            TypeSpec.Builder clsBuilder = TypeSpec.classBuilder(target.simpleName() + "Unbinder")
                    .addSuperinterface(UNBINDER)
                    .addModifiers(Modifier.PUBLIC)
                    .addField(target, "target", Modifier.PRIVATE)
                    .addMethod(ctor1);
    
    
            // 方法和字段绑定
            List<Pair<FieldBinding, MethodBinding>> fieldMethods = merge();
    
            for (Pair<FieldBinding, MethodBinding> fieldMethod : fieldMethods) {
                if (fieldMethod == null) continue;
                FieldBinding field = fieldMethod.getKey();
                MethodBinding method = fieldMethod.getValue();
    
                // 只绑定字段
                if (field != null && method == null)
                    brewOnlyField(field, ctorBuilder, unbind);
                    // 只绑定方法
                else if (field == null && method != null)
                    brewOnlyMethod(method, ctorBuilder, clsBuilder, unbind);
                    // 绑定 字段和方法
                else brewFieldMethod(field, method, ctorBuilder, unbind);
            }
    
            unbind.addStatement("target = null");
    
            clsBuilder.addMethod(ctorBuilder.build())
                    .addMethod(unbind.build());
    
            return JavaFile.builder(target.packageName(), clsBuilder.build())
                    .build();
        }
    
        private void brewOnlyField(FieldBinding field, MethodSpec.Builder ctorBuilder, MethodSpec.Builder unbind) {
            ClassName fieldType = field.getFieldType();
            ctorBuilder.addStatement("target.$L = $T.findRequiredViewAsType(source, $L, \"field '$L'\", $T.class)",
                    field.getFieldName(), UTILS, field.getValue(), field.getFieldName(), fieldType);
            unbind.addStatement("target.$L = null", field.getFieldName());
        }
    
        private void brewOnlyMethod(MethodBinding method, MethodSpec.Builder ctorBuilder, TypeSpec.Builder clsBuilder, MethodSpec.Builder unbind) {
            String methodName = method.getMethodName();
            int value = method.getValue();
            String member = "view" + value;
            TypeSpec anonymous = brewAnonymousClass(methodName, method.hasParam());
    
            // 添加成员变量
            clsBuilder.addField(VIEW, member, Modifier.PRIVATE);
            // view7f080024 = Utils.findRequiredView(source, R.id.btn, "method 'click'");
            ctorBuilder.addStatement("$L = $T.findRequiredView(source, $L, \"method 'click'\")",
                    member, UTILS, value);
            ctorBuilder.addStatement("$L.setOnClickListener($L)", member, anonymous);
    
            unbind.addStatement("$L.setOnClickListener(null)", member);
            unbind.addStatement("$L = null", member);
        }
    
        private void brewFieldMethod(FieldBinding field, MethodBinding method, MethodSpec.Builder ctorBuilder, MethodSpec.Builder unbind) {
            ClassName fieldType = field.getFieldType();
            String methodName = method.getMethodName();
    
            ctorBuilder.addStatement("target.$L = $T.findRequiredViewAsType(source, $L, \"field '$L' and method '$L'\", $T.class)",
                    field.getFieldName(), UTILS, field.getValue(), field.getFieldName(), methodName, fieldType);
    
            TypeSpec anonymous = brewAnonymousClass(methodName, method.hasParam());
    
            // target.view.setOnClickListener(new DebouncingOnClickListener() {
            ctorBuilder.addStatement("target.$L.setOnClickListener($L)", field.getFieldName(), anonymous);
    
            unbind.addStatement("target.$L.setOnClickListener(null)", field.getFieldName());
            unbind.addStatement("target.$L = null", field.getFieldName());
        }
    
        private TypeSpec brewAnonymousClass(String name, boolean hasParam) {
            String statement = hasParam ? "target.$L(v)" : "target.$L()";
            return TypeSpec.anonymousClassBuilder("")
                    .addSuperinterface(LISTENER)
                    .addMethod(MethodSpec.methodBuilder("doClick")
                            .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC)
                            .addParameter(VIEW, "v")
                            .addStatement(statement, name)
                            .build())
                    .build();
        }
    
        // 合并 字段绑定 和 方法绑定相关
        private List<Pair<FieldBinding, MethodBinding>> merge() {
            List<Pair<FieldBinding, MethodBinding>> list = new ArrayList<>();
            boolean hasAdd = false;
    
            for (FieldBinding fieldBinding : fieldBindings) {
                for (MethodBinding methodBinding : methodBindings) {
                    if (fieldBinding.getValue() == methodBinding.getValue()) {
                        list.add(new Pair<>(fieldBinding, methodBinding));
                        hasAdd = true;
                        break;
                    }
                }
                if (!hasAdd) list.add(new Pair<>(fieldBinding, null));
                else hasAdd = false;
            }
    
            for (MethodBinding methodBinding : methodBindings) {
                for (FieldBinding fieldBinding : fieldBindings) {
                    if (fieldBinding.getValue() == methodBinding.getValue()) {
                        hasAdd = true;
                        break;
                    }
                }
                if (!hasAdd) list.add(new Pair<>(null, methodBinding));
                else hasAdd = false;
            }
            return list;
        }
    }
    

    接下来,我们来解析 注解数据,一下的代码出现在

    先对 @BindView 的注解进行解析 class ButterKnifeProcessor extends AbstractProcessorprocess方法中.

     // 绑定ViewId 遍历所有被注解了@BindView的元素
            for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
                // 判断注释 是否在 字段上
                if (!(element instanceof VariableElement) || element.getKind() != ElementKind.FIELD) {
                    throw new IllegalStateException("@BindView annotation must be on a field.");
                }
    
                // 获取目标类的类型 如(xxx.MainActivity)
                TypeElement typeElement = (TypeElement) element.getEnclosingElement();
                String targetClassName = typeElement.getQualifiedName().toString();
    
                // 根据类名来生成文件, 一个被注解的类中所有的注解对应生成一份类文件
                ViewBinding binding = bindingMap.get(targetClassName);
                if (binding == null) {
                    binding = new ViewBinding(ClassName.bestGuess(targetClassName));
                    bindingMap.put(targetClassName, binding);
                }
    
                // 获取注解上的值(这里是R.id对应的int值)
                int value = element.getAnnotation(BindView.class).value();
                // 获取字段的类型
                ClassName fieldClass = (ClassName) ClassName.get(element.asType());
    
                // 判断字段类型是否是View的子类型
                if (!Utils.isSubtypeOfType(element.asType(), "android.view.View")) {
                    throw new IllegalStateException("field type must be the child of android.view.View");
                }
    
                // 获取字段的名称
                String name = element.getSimpleName().toString();
    
                // 添加字段绑定
                FieldBinding field = new FieldBinding(value, fieldClass, name);
                binding.addFiled(field);
    
                shouldWrite = true;
            }
    

    然后对 @OnClick注解进行解析

            // 绑定监听事件 遍历所有被注解了@OnClick的元素
            for (Element element : roundEnv.getElementsAnnotatedWith(OnClick.class)) {
                // 判断注释 是否在 方法上
                if (!(element instanceof ExecutableElement) || element.getKind() != ElementKind.METHOD) {
                    throw new IllegalStateException("@Click annotation must be on a method.");
                }
    
                ExecutableElement executableElement = (ExecutableElement) element;
                TypeElement typeElement = (TypeElement) element.getEnclosingElement();
    
                // 获取目标类的类型 如(xxx.MainActivity)
                String targetName = typeElement.getQualifiedName().toString();
    
                // 添加方法绑定
                ViewBinding binding = bindingMap.get(targetName);
                if (binding == null) {
                    binding = new ViewBinding(ClassName.bestGuess(targetName));
                    bindingMap.put(targetName, binding);
                }
    
                // 获取方法的参数列表
                List<? extends VariableElement> methodParameters = executableElement.getParameters();
                // 获取方法参数的个数
                int methodParameterSize = methodParameters.size();
                // 参数校验
                if (methodParameterSize > 1) {
                    throw new IllegalStateException("@Click method only has one param, and must be android.view.View");
                } else if (methodParameterSize == 1) {
                    VariableElement param = methodParameters.get(0);
                    TypeName paramType = TypeName.get(param.asType());
                    if (!paramType.toString().equals("android.view.View")) {
                        throw new IllegalStateException("@Click method only has one param, and must be android.view.View");
                    }
                }
    
                // 获取返回类型
                TypeName returnType = TypeName.get(executableElement.getReturnType());
                if (!returnType.toString().equals("void")) {
                    throw new IllegalStateException("@Click method return type must be void");
                }
    
                // 获取注解上的值 [R.id.x1,R.id.x2]
                int[] values = element.getAnnotation(OnClick.class).value();
                if (values.length == 0) continue;
    
                // 获取注解上的方法名
                String methodName = element.getSimpleName().toString();
    
                // 添加 方法绑定
                for (int value : values) {
                    MethodBinding method = new MethodBinding(methodName, value, methodParameterSize == 1);
                    binding.addMethod(method);
                }
    
                shouldWrite = true;
            }
    

    最后对解析的数据生成 样板代码.

     if (shouldWrite) {
                // 生成java文件
                for (String key : bindingMap.keySet()) {
                    ViewBinding binding = bindingMap.get(key);
                    JavaFile javaFile = binding.brewJava();
                    try {
                        javaFile.writeTo(filer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    这样我们的 注解解析和生成样板代码的过程就结束.

    我们来验证下, 在 MainActivity中,声明一下字段和方法.

        @BindView(R.id.btn2)
        Button btn2;
        @BindView(R.id.btn)
        Button button;
    
        int id = 0;
    
        @OnClick({R.id.btn, R.id.btn2})
        void click(View view) {
            Toast.makeText(this, "i am a toast !!", Toast.LENGTH_SHORT).show();
        }
    

    然后, 运行 gradlecompileDebugJavaWithJavac脚本, 就会自动生成一份 MainActivityUnbinder的样板文件.

    public class MainActivityUnbinder implements Unbinder {
      private MainActivity target;
    
      public MainActivityUnbinder(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    
      public MainActivityUnbinder(final MainActivity target, View source) {
        this.target = target;
        target.btn2 = Utils.findRequiredViewAsType(source, 2131230757, "field 'btn2' and method 'click'", Button.class);
        target.btn2.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View v) {
            target.click(v);
          }
        });
        target.button = Utils.findRequiredViewAsType(source, 2131230756, "field 'button' and method 'click'", Button.class);
        target.button.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View v) {
            target.click(v);
          }
        });
      }
    
      @Override
      public void unbind() {
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        target.btn2.setOnClickListener(null);
        target.btn2 = null;
        target.button.setOnClickListener(null);
        target.button = null;
        target = null;
      }
    }
    

    butterknife模块

    这个模块是用于反射调用 样板代码的.

    public class ButterKnife {
        private static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
    
        @UiThread
        public static Unbinder bind(@NonNull Activity target) {
            View sourceView = target.getWindow().getDecorView();
            return bind(target, sourceView);
        }
    
        @UiThread
        public static Unbinder bind(@NonNull Object target, @NonNull View source) {
            Class<?> targetClass = target.getClass();
            Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    
            if (constructor == null) {
                return Unbinder.EMPTY;
            }
    
            try {
                return constructor.newInstance(target, source);
            } catch (Exception e) {
                throw new RuntimeException("Unable to create binding instance.", e.getCause());
            }
        }
    
        @UiThread
        private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
            Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
            if (bindingCtor != null) {
                return bindingCtor;
            }
            String clsName = cls.getName();
            try {
    
                Class<?> bindingClass = Objects.requireNonNull(cls.getClassLoader()).loadClass(clsName + "Unbinder");
                //noinspection unchecked
                bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
            } catch (Exception e) {
                throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
            }
            BINDINGS.put(cls, bindingCtor);
            return bindingCtor;
        }
    }
    

    这种代码,比较容易理解, 就不做过多说明, 这里说明下 其他两个类.DebouncingOnClickListenerUnbinder.

    DebouncingOnClickListener类的作用是,避免在极短的时间内重复的过快的响应点击事件.

    public abstract class DebouncingOnClickListener implements View.OnClickListener {
        static boolean enabled = true;
    
        private static final Runnable ENABLE_AGAIN = () -> enabled = true;
    
        @Override public final void onClick(View v) {
            if (enabled) {
                enabled = false;
                v.post(ENABLE_AGAIN);
                doClick(v);
            }
        }
    
        public abstract void doClick(View v);
    }
    

    Unbinder 接口是 样板代码 都要实现的接口, 用于监听事件 和 资源的移除.

    public interface Unbinder {
       Unbinder EMPTY = () -> {};
    
        void unbind();
    }
    

    至此, 实现 ButterKnife@BindView@OnClick功能, 都以实现.

    感兴趣的同学, 可以到 mini-butterknife,下载源码查看

    引用

    1. butterknife
    2. JavaPoet

    相关文章

      网友评论

          本文标题:Butterknife深入理解之自己动手编写Butterknif

          本文链接:https://www.haomeiwen.com/subject/ydafcqtx.html