ButterKnife实现(事件初始化)

时间:2021-06-25 19:51:39
一个星期没更新博客了,虽然目前博客很乱!最近比较忙,视力有些下降,不过ButterKnife的事件实现终算完成了!

新博客地址

ButterKnifeDemo实现(注解完善,方便阅读)

目标

之所以butterknife可以实现点击view的时候调用注解过的方法,其实是在点击的回调方法中调用目标类的相应注释过的方法:

view.setOnClickListener(new DebouncingOnClickListener() {
            @Override
            public void doClick(View v) {
            //HomeActivity.click(View v)
                target.click(v);
            }
        });

拿我的demo为例,我们最终是要生成一个如下的类:

这里写代码片

package com.turbo.demo.apt;
import android.view.View;
import com.turbo.apt.library.internal.DebouncingOnClickListener;
import com.turbo.apt.library.internal.Finder;
import com.turbo.apt.library.internal.ViewBinder;

/** * Created by LuckyTurbo on 16/4/11. * * 待生成的类 */
public class MainActivity_ViewBinder<T extends MainActivity> implements ViewBinder<T> {
    @Override
    public void bind(Finder finder, final T target, Object source) {
        View view = null;
        view = finder.findRequiredView(source,R.id.rightView,"rightView 我 啊");
        target.rightView = finder.castView(view,R.id.rightView,"rightView 我 啊");
        view.setOnClickListener(new DebouncingOnClickListener() {
            @Override
            public void doClick(View v) {
                target.click(v);
            }
        });

    }
}

那么下面要做的就是收集各种注解数据,然后再根据这些数据生成最终的类。

收集数据

绝大部分方法的数据都是从注解中获取的,那么我们如何设计注解呢?

注解设计分析

需求:
1.id
2.设置方法 (example:setOnLongClickListener)
3.接口类型 (android.view.View.OnLongClickListener)
4.接口中的方法:
    1.方法名 (onLongClick)
    2.参数类型 (android.view.View)
    3.方法返回值 (boolean)

这么多东西如果在每个注解里面都加上,想想都恶心,但是注解并没有继承这一说,但是我们可以在注解的基础上再进行注解,上层注解里面可以设定默认值,每次给上层注解填值就可以了
——其实我们想要的就是规范,就是模版,其实就是针对规范编程

下面看下主要的几个注解:

  • @OnClick
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@ListenerClass(
        targetType = "android.view.View",
        setter = "setOnClickListener",
        // 这个是butterKnife中的防卡点击
        type = "com.turbo.apt.library.internal.DebouncingOnClickListener",
        method = @ListenerMethod(
                name = "doClick",
                parameters = "android.view.View",
                returnType = "void"
        )
)
public @interface OnClick {
    int[] value() default {View.NO_ID};
}
  • @ListenerClass
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)// 靠,获取注解的注解好像必需要使用runtime,不然取不到
public @interface ListenerClass {
    // @ListenerClass(
    // targetType = "android.view.View",
    // setter = "setOnClickListener",
    // type = "butterknife.internal.DebouncingOnClickListener",
    // method = @ListenerMethod(
    // name = "doClick",
    // parameters = "android.view.View"
    // )
    // )

    // 某view
    String targetType();
    // 设置方法的名称
    String setter();
    // 接口全称
    String type();
    /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */
    // 跟method互斥
    Class<? extends Enum<?>> callbacks() default NONE.class;
    /** * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()} * and an error to specify more than one value. */
    ListenerMethod method();

    // callback的默认值
    enum NONE {}
}
  • @ListenerMethod
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ListenerMethod {

    // listener 方法 的 名字
    String name();

    // 方法参数
    String[] parameters() default {};

    // 方法返回类型
    String returnType() default "void";

    /** If {@link #returnType()} is not {@code void} this value is returned when no binding exists. */
    // 如果returnType 不是void,就返回这个值
    String defaultReturn() default "null";
}

注解收集数据结构

ButterKnife实现(事件初始化)

注解收集数据逻辑

private Map<TypeElement, VBinderBuilder> findAndParseTargets(RoundEnvironment roundEnv) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Map<TypeElement, VBinderBuilder> vBinderBuilderMap = new LinkedHashMap<>();

        // 解析所有的@Bind 元素
        for (Element element : roundEnv.getElementsAnnotatedWith(Bind.class)) {
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            VBinderBuilder builderClass = null;
            if (vBinderBuilderMap.containsKey(typeElement)) {
                builderClass = vBinderBuilderMap.get(typeElement);
            } else {
                String targetType = typeElement.getQualifiedName().toString();
                String classPackage = getPackageName(typeElement);
                // packageName$className_ViewBinder
                String className = getClassName(typeElement, classPackage) + APTConfig.SUFFIX;

                builderClass = new VBinderBuilder(classPackage, className, targetType);
                vBinderBuilderMap.put(typeElement, builderClass);
            }

            int value = element.getAnnotation(Bind.class).value();
            String elementName = element.getSimpleName().toString();
            TypeName typeName = TypeName.get(element.asType());
            FieldViewBinding fieldViewBinding = new FieldViewBinding(elementName, typeName, value);
            builderClass.addField(value, fieldViewBinding);
        }
        // 解析所有LISTENERS:方法上的事件注解
        for (Class<? extends Annotation> annotationClass : LISTENERS) {
            for (Element element : roundEnv.getElementsAnnotatedWith(annotationClass)) {
                // 方法元素
                ExecutableElement executableElement = (ExecutableElement) element;
                // 类元素(持有者元素)
                TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
                // 方法的注解对象
                Annotation annotation = element.getAnnotation(annotationClass);
                // 注解的value方法
                Method annotationValue = annotationClass.getDeclaredMethod("value");
                // 获取注解的ids
                int[] ids = (int[]) annotationValue.invoke(annotation);
                // 方法名称
                String name = executableElement.getSimpleName().toString();
                // 注解的注解
                ListenerClass listenerClass = annotationClass.getAnnotation(ListenerClass.class);
                // 注解注解的参数
                ListenerMethod listenerMethod = listenerClass.method();
                // 方法参数
                List<? extends VariableElement> methodParameters = executableElement.getParameters();

                Parameter[] parameters = Parameter.NONE;
                // 真实方法参数
                if (!methodParameters.isEmpty()) {
                    parameters = new Parameter[methodParameters.size()];
                    // 注解的注解中的参数类型
                    String[] parameterTypes = listenerMethod.parameters();

                    for (int i = 0; i < methodParameters.size(); i++) {
                        VariableElement methodParameter = methodParameters.get(i);
                        // 方法参数
                        TypeMirror methodParameterType = methodParameter.asType();
                        if (methodParameterType instanceof TypeVariable) {
                            TypeVariable typeVariable = (TypeVariable) methodParameterType;
                            methodParameterType = typeVariable.getUpperBound();
                            messager.printMessage(Diagnostic.Kind.ERROR, ((TypeVariable) methodParameterType).asElement().getSimpleName());
                        }
                        for (int j = 0; j < parameterTypes.length; j++) {
                            // 封装了真实参数的位置,类型
                            parameters[j] = new Parameter(j, TypeName.get(methodParameterType));
                        }
                    }
                }
                // name:方法名 parameters:方法参数parameter required:方法上是否有Optional注解(一般不会有,所以为true)
                // 一般都是true,我们这里没有给注解设置Optional注解
                MethodViewBinding methodViewBinding = new MethodViewBinding(name, Arrays.asList(parameters), true);
                VBinderBuilder builderClass = getOrCreateTargetClass(vBinderBuilderMap, enclosingElement);
                for (int id : ids){
                    builderClass.addMethod(id,listenerClass,listenerMethod,methodViewBinding);
                }
            }
        }
        return vBinderBuilderMap;
    }

ViewBinder绑定帮助类生成

根据上一篇的实现步骤,我们生成一个实现ViewBinder接口,及其方法,实现控件初始化的类已经不是一件难事,现在最大的困难就是如何通过JavaPoet实现类似下面的代码:

view.setOnClickListener(new DebouncingOnClickListener() {
            @Override
            public void doClick(View v) {
                target.click(v);
            }
        });

根据butterKnife代码我给出具体的解决步骤:
以@OnLongClick为例:

// new DebouncingOnClickListener()
// 这里就是一个空接口以及一个父类
TypeSpec.Builder interfaceEventBuilder = TypeSpec.anonymousClassBuilder("")
                    .superclass(ClassName.bestGuess(keyListenerClass.type()));
// 这里代表的是 方法:public void doClick
MethodSpec.Builder interfaceMethodBuilder = MethodSpec.methodBuilder(method.name())
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.PUBLIC)
                        .returns(bestGuess(method.returnType()));
// 第一个参数 是参数类型 第一个参数是参数名(p0 p1 p2 ...)
// 方法中的参数(View v)
                   interfaceMethodBuilder.addParameter(bestGuess(parameterTypes[i]), "p" + i);
// 调用target的对应方法:target.click(v);
codeBuilder.add("target.$L(", methodViewBinding.getName());

整个方法的生成代码如下:

    private void addMethodBindings(MethodSpec.Builder resultBuilder, VBinderData vbinderData) {
        Map<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> listenerMethodBindings = vbinderData.getMethodBindings();
        if (listenerMethodBindings.isEmpty()) {
            return;
        }
        Set<Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>>> entries = listenerMethodBindings.entrySet();
        for (Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> entry : entries) {
            ListenerClass keyListenerClass = entry.getKey();
            Map<ListenerMethod, Set<MethodViewBinding>> methodValue = entry.getValue();
            // 创建接口实体类(空匿名类的接口父类)
            // anonymous 匿名
            TypeSpec.Builder interfaceEventBuilder = TypeSpec.anonymousClassBuilder("")
                    .superclass(ClassName.bestGuess(keyListenerClass.type()));
            // 根据ListenerClass 中的 参数 生成代码
            ListenerMethod method = keyListenerClass.method();
            if (method != null) {
                MethodSpec.Builder interfaceMethodBuilder = MethodSpec.methodBuilder(method.name())
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.PUBLIC)
                        .returns(bestGuess(method.returnType()));
                String[] parameterTypes = method.parameters();
                for (int i = 0; i < parameterTypes.length; i++) {
                    // 第一个参数  是参数类型  第一个参数是参数名(p0 p1 p2 ...)
                    interfaceMethodBuilder.addParameter(bestGuess(parameterTypes[i]), "p" + i);
                }

                boolean hasReturnType = !"void".equals(method.returnType());
                // 代码块
                CodeBlock.Builder codeBuilder = CodeBlock.builder();
                if (hasReturnType) {
                    codeBuilder.add("return ");
                }

                // 一个id对应多个ListenerMethod
                if (methodValue.containsKey(method)) {
                    for (MethodViewBinding methodViewBinding : methodValue.get(method)) {
                        // 调用target的对应方法
                        codeBuilder.add("target.$L(", methodViewBinding.getName());
                        List<Parameter> parameters = methodViewBinding.getParameters();
                        // MethodListener注解的参数
                        String[] methodParameters = method.parameters();
                        // 优化了每次parameters.size()
                        for (int i = 0, count = parameters.size(); i < count; i++) {
                            if (i > 0) {
                                codeBuilder.add(", ");
                            }
                            Parameter parameter = parameters.get(i);
                            int listenerPosition = parameter.getListenerPosition();
                            // 类型不一样,范型万能转换
                            if (parameter.requiresCast(methodParameters[i])) {
                                codeBuilder.add("finder.<$T>castParam(p$L, $S, $L, $S, $L)\n", parameter.getType(),
                                        listenerPosition, method.name(), listenerPosition, methodViewBinding.getName(), i);
                            } else {
                                codeBuilder.add("p$L", listenerPosition);
                            }
                        }
                        codeBuilder.add(");\n");
                    }
                } else if (hasReturnType) {
                    codeBuilder.add("$L;\n", method.defaultReturn());
                }
                interfaceMethodBuilder.addCode(codeBuilder.build());
                interfaceEventBuilder.addMethod(interfaceMethodBuilder.build());
            }
            // 如果不是view类型,需要强转
            if (!APTConfig.VIEW_TYPE.equals(keyListenerClass.targetType())) {
                // targetType不是view的时候需要强转
                resultBuilder.addStatement("(($T) view).$L($L)", bestGuess(keyListenerClass.targetType()),
                        keyListenerClass.setter(), interfaceEventBuilder.build());
            } else {
                // view.setOnClickListener(对象)
                resultBuilder.addStatement("view.$L($L)", keyListenerClass.setter(), interfaceEventBuilder.build());
            }
        }
    }

其他的具体步骤大家看下demo

总结

代码这个东西,看跟敲是两种体验,也是两种结果,以后会继续坚持,总结下从butterKnife框架细节中我学到的知识:

  • BitSet : java中提供的此神器用来记录我对一组数据的改动与否
  • @Retention(RUNTIME) :注解的注解是需要runtime的,即便仅在编译期读取,RetentionPolicy.CLASS也是不奏效的,必需是RetentionPolicy.RUNTIME
  • for (int i = 0, count = parameters.size(); i < count; i++) : 省去每次遍历调用parameters.size()的同时,又优雅的初始化了count并附上了想要的值(智慧而优雅)