ButterKnife系列之手写实现

注解java-library

build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

// 中文乱码问题(错误: 编码GBK的不可映射字符)
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

BindView 注解

// SOURCE 注解仅在源码中保留,class文件中不存在
// CLASS 注解在源码和class文件中都存在,但运行时不存在
// RUNTIME 注解在源码,class文件中存在且运行时可以通过反射机制获取到
@Target(ElementType.FIELD) // 注解作用在属性之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface  BindView {

    // 返回R.id.xx值
    int value();
}

OnClick 注解

@Target(ElementType.METHOD) // 注解作用在方法之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface OnClick {

    // 此处省略了int[]
    int value();
}

注解使用的实体类或者接口

ViewBinder 接口

/**
 * 接口绑定类(所有注解处理器生的类,都需要实现该接口,= 接口实现类)
 */
public interface ViewBinder<T> {

    void bind(T target);
}

DebouncingOnClickListener 抽象类


// 点击监听接口,实现类(抽象类 + 抽象方法)
public abstract class DebouncingOnClickListener implements View.OnClickListener {

    @Override
    public void onClick(View v) {
        // 调用抽象方法
        doClick(v);
    }

    public abstract void doClick(View v);
}

ButterKnife 核心类

/**
 * 核心类,接口 = 接口实现类
 * ButterKnife用的是构造方法.newInstance()
 * 接口.bind()
 */
public class ButterKnife {

    public static void bind(Activity activity) {
        // 拼接类名,如:MainActivity$ViewBinder
        String className = activity.getClass().getName() + "$ViewBinder";

        try {
            // 加载上述拼接类
            Class<?> viewBinderClass = Class.forName(className);
            // 接口 = 接口实现类
            ViewBinder viewBinder = (ViewBinder) viewBinderClass.newInstance();
            // 调用接口方法
            viewBinder.bind(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注解处理器java-library

build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // As3.5 + gradle5.4.1-all
    compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

    // 帮助我们通过类调用的形式来生成Java代码
    implementation "com.squareup:javapoet:1.10.0"
    // 引入annotation,处理@BindView、@Onclick注解
    implementation project(':annotation')
}

// 中文乱码问题(错误: 编码GBK的不可映射字符)
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

ButterKnifeProcess 注解处理核心类

// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.BINDVIEW_ANNOTATION_TYPES, Constants.ONCLICK_ANNOTATION_TYPES})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ButterKnifeProcess extends AbstractProcessor {

    // 操作Element工具类 (类、函数、属性都是Element)
    private Elements elementUtils;

    // type(类信息)工具类,包含用于操作TypeMirror的工具方法
    private Types typeUtils;

    // Messager用来报告错误,警告和其他提示信息
    private Messager messager;

    // 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件
    private Filer filer;

    // key:类节点, value:被@BindView注解的属性集合
    private Map<TypeElement, List<VariableElement>> tempBindViewMap = new HashMap<>();

    // key:类节点, value:被@OnClick注解的方法集合
    private Map<TypeElement, List<ExecutableElement>> tempOnClickMap = new HashMap<>();

    // 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        // 初始化
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        messager.printMessage(Diagnostic.Kind.NOTE,
                "注解处理器初始化完成,开始处理注解------------------------------->");
    }

    /**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param set              使用了支持处理注解的节点集合
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解。
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 一旦有属性上使用@BindView注解
        if (!EmptyUtils.isEmpty(set)) {
            // 获取所有被 @BindView 注解的 元素集合
            Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
            // 获取所有被 @OnClick 注解的 元素集合
            Set<? extends Element> onClickElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);

            if (!EmptyUtils.isEmpty(bindViewElements) || !EmptyUtils.isEmpty(onClickElements)) {
                try {
                    // 赋值临时map存储,用来存放被注解的属性集合
                    valueOfMap(bindViewElements, onClickElements);
                    // 生成类文件,如:
                    createJavaFile();
                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    private void createJavaFile() throws IOException {

        // 判断是否有需要生成的类文件
        if (!EmptyUtils.isEmpty(tempBindViewMap)) {
            // 获取ViewBinder接口类型(生成类文件需要实现的接口)
            TypeElement viewBinderType = elementUtils.getTypeElement(Constants.VIEWBINDER);
            TypeElement clickListenerType = elementUtils.getTypeElement(Constants.CLICKLISTENER);
            TypeElement viewType = elementUtils.getTypeElement(Constants.VIEW);

            for (Map.Entry<TypeElement, List<VariableElement>> entry : tempBindViewMap.entrySet()) {
                // 类名
                ClassName className = ClassName.get(entry.getKey());
                // 实现接口泛型
                ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBinderType),
                        ClassName.get(entry.getKey()));
                // 参数体配置(MainActivity target)
                ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), // MainActivity
                        Constants.TARGET_PARAMETER_NAME) // target
                        .addModifiers(Modifier.FINAL)
                        .build();

                // 方法配置:public void bind(MainActivity target) {
                MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME) // 方法名
                        .addAnnotation(Override.class) // 重写注解
                        .addModifiers(Modifier.PUBLIC) // public修饰符
                        .addParameter(parameterSpec); // 方法参数

                for (Element fieldElement : entry.getValue()) {
                    // 获取属性名
                    String fieldName = fieldElement.getSimpleName().toString();
                    // 获取@BindView注解的值
                    int annotationValue = fieldElement.getAnnotation(BindView.class).value();
                    // target.tv = target.findViewById(R.id.tv);
                    String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
                    methodBuidler.addStatement(methodContent,
                            Constants.TARGET_PARAMETER_NAME,
                            Constants.TARGET_PARAMETER_NAME,
                            annotationValue);
                }

                if (!EmptyUtils.isEmpty(tempOnClickMap)) {
                    for (Map.Entry<TypeElement, List<ExecutableElement>> methodEntry : tempOnClickMap.entrySet()) {
                        // 类名
                        if (className.equals(ClassName.get(entry.getKey()))) {
                            for (ExecutableElement methodElement : methodEntry.getValue()) {
                                // 获取方法名
                                String methodName = methodElement.getSimpleName().toString();
                                // 获取@OnClick注解的值
                                int annotationValue = methodElement.getAnnotation(OnClick.class).value();
                                /**
                                 * target.findViewById(2131165312).setOnClickListener(new DebouncingOnClickListener() {
                                 *      public void doClick(View view) {
                                 *          target.click(view);
                                 *      }
                                 * });
                                 */
                                methodBuidler.beginControlFlow("$N.findViewById($L).setOnClickListener(new $T()",
                                        Constants.TARGET_PARAMETER_NAME, annotationValue, ClassName.get(clickListenerType))
                                        .beginControlFlow("public void doClick($T view)", ClassName.get(viewType))
                                        .addStatement("$N." + methodName + "(view)", Constants.TARGET_PARAMETER_NAME)
                                        .endControlFlow()
                                        .endControlFlow(")")
                                        .build();
                            }
                        }
                    }
                }

                // 必须是同包(属性修饰符缺省),MainActivity$$ViewBinder
                JavaFile.builder(className.packageName(), // 包名
                        TypeSpec.classBuilder(className.simpleName() + "$ViewBinder") // 类名
                                .addSuperinterface(typeName) // 实现ViewBinder接口
                                .addModifiers(Modifier.PUBLIC) // public修饰符
                                .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
                                .build()) // 类构建完成
                        .build() // JavaFile构建完成
                        .writeTo(filer); // 文件生成器开始生成类文件
            }
        }
    }

    private void valueOfMap(Set<? extends Element> bindViewElements, Set<? extends Element> onClickElements) {
        if (!EmptyUtils.isEmpty(bindViewElements)) {
            for (Element element : bindViewElements) {
                messager.printMessage(Diagnostic.Kind.NOTE, "@BindView >>> " + element.getSimpleName());
                if (element.getKind() == ElementKind.FIELD) {
                    VariableElement fieldElement = (VariableElement) element;
                    // 注解在属性之上,属性节点父节点是类节点
                    TypeElement enclosingElement = (TypeElement) fieldElement.getEnclosingElement();
                    // 如果map集合中的key:类节点存在,直接添加属性
                    if (tempBindViewMap.containsKey(enclosingElement)) {
                        tempBindViewMap.get(enclosingElement).add(fieldElement);
                    } else {
                        List<VariableElement> fields = new ArrayList<>();
                        fields.add(fieldElement);
                        tempBindViewMap.put(enclosingElement, fields);
                    }
                }
            }
        }

        if (!EmptyUtils.isEmpty(onClickElements)) {
            for (Element element : onClickElements) {
                messager.printMessage(Diagnostic.Kind.NOTE, "@OnClick >>> " + element.getSimpleName());
                if (element.getKind() == ElementKind.METHOD) {
                    ExecutableElement methodElement = (ExecutableElement) element;
                    // 注解在属性之上,属性节点父节点是类节点
                    TypeElement enclosingElement = (TypeElement) methodElement.getEnclosingElement();
                    // 如果map集合中的key:类节点存在,直接添加属性
                    if (tempOnClickMap.containsKey(enclosingElement)) {
                        tempOnClickMap.get(enclosingElement).add(methodElement);
                    } else {
                        List<ExecutableElement> fields = new ArrayList<>();
                        fields.add(methodElement);
                        tempOnClickMap.put(enclosingElement, fields);
                    }
                }
            }
        }
    }
}

Constants 常量类

/**
 * 常量类
 */
public class Constants {

    // 注解处理器中支持的注解类型
    public static final String BINDVIEW_ANNOTATION_TYPES = "com.sanguine.butterknife.annotation.BindView";
    public static final String ONCLICK_ANNOTATION_TYPES = "com.sanguine.butterknife.annotation.OnClick";

    // 布局、控件绑定实现接口
    public static final String VIEWBINDER = "com.sanguine.butterknife.library.ViewBinder";

    public static final String CLICKLISTENER = "com.sanguine.butterknife.library.DebouncingOnClickListener";

    public static final String VIEW = "android.view.View";

    // bind方法名
    public static final String BIND_METHOD_NAME = "bind";

    // bind方法的参数名target
    public static final String TARGET_PARAMETER_NAME = "target";
}

EmptyUtils 判空类


/**
 * 字符串、集合判空工具
 */
public final class EmptyUtils {


    public static boolean isEmpty(CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

    public static boolean isEmpty(Collection<?> coll) {
        return coll == null || coll.isEmpty();
    }

    public static boolean isEmpty(final Map<?, ?> map) {
        return map == null || map.isEmpty();
    }

}

总结

本文通过APT+JavaPoet技术实现编译器解析注解,并且生成Java文件。APT负责解析注解,JavaPoet负责生成Java文件。最终实现了ButterKnife。在之前组件化的时候,我们同样有用过这样的技术,当时是通过生成的问题实现组件化之间的通信。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值