手写一个 ButterKnife

67 阅读8分钟

1. 创建注解

1.1 声明在什么类型上

@Target(ElementType.FIELD)

ElementType类型

public enum ElementType {
//    类、接口(包括注解类型)或枚举声明。
    TYPE,
//    成员变量声明(包括枚举常量)。
    FIELD,
//    方法声明。
    METHOD,
//    参数声明。
    PARAMETER,
//    构造方法声明。
    CONSTRUCTOR,
//    局部变量声明。
    LOCAL_VARIABLE,
//    注解类型声明。
    ANNOTATION_TYPE,
//    包声明。
    PACKAGE,
//    泛型声明
    TYPE_PARAMETER,
//    没有使用限制的声明   以上都包含
    TYPE_USE;
}

1.2 声明注解的生命周期

@Retention(RetentionPolicy.CLASS)

** RetentionPolicy 类型 **

public enum RetentionPolicy {
//    源码期
    SOURCE,
//    编译期
    CLASS,
//    运行期
    RUNTIME;

    private RetentionPolicy() {
    }
}

2. 创建注解处理器(必须是 Java/Kotlin 的 jar 项目)

目的 在运行时生成对应的类,处理对应的逻辑的代码 注意事项 注解处理器,不能使用 log 打印日志,也不能进行 debug 运行

2.1 引入第三方库

dependencies {
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    implementation 'com.google.auto.service:auto-service:1.0-rc7'
}

2.2 创建注解处理器类,并声明 Java 支持的版本和需要处理的注解有哪些(有以下两种方法)

2.2.1 使用代码声明

/**
 * 注解处理器
 *
 * @// TODO: 2024/1/4 创建对应的类,比如viewHolder 处理对应的逻辑
 */
@AutoService(Processor.class)           // 注册这个类是注解处理器
public class AnnotationCompiler extends AbstractProcessor {

    /**
     * 声明注解处理器支持的 Java 版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
//        Java 当前版本
        return processingEnv.getSourceVersion();
    }

    /**
     * 声明注解处理器要处理的注解有哪些
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
//        返回的是 BindView 注解的规范化类名(Canonical Name)。规范化类名是一个完全限定的类名,包括包路径,但不包括内部类或简化的类名。
        types.add(BindView.class.getCanonicalName());
//        返回的是 OnClick 注解的规范化类名(Canonical Name)。规范化类名是一个完全限定的类名,包括包路径,但不包括内部类或简化的类名。
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

    /**
     * 打印日志
     */
    public void logUtil(String log) {
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, log);
    }
}

2.2.2 使用注解声明

/**
 * 注解处理器
 *
 * @// TODO: 2024/1/4 创建对应的类,比如viewHolder 处理对应的逻辑
 */
@AutoService(Processor.class)           // 注册这个类是注解处理器
@SupportedAnnotationTypes({"包名+类名", "包名+类名"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationCompiler extends AbstractProcessor {

    /**
     * 打印日志
     */
    public void logUtil(String log) {
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, log);
    }
}

2.3 创建一个容器,用来保存成员变量和方法注解

public class ElementForType {

//    成员变量的节点
    private List<VariableElement> viewElement;
//    方法的节点
    private List<ExecutableElement> methodElement;

    public List<VariableElement> getViewElement() {
        return viewElement;
    }

    public void setViewElement(List<VariableElement> viewElement) {
        this.viewElement = viewElement;
    }

    public List<ExecutableElement> getMethodElement() {
        return methodElement;
    }

    public void setMethodElement(List<ExecutableElement> methodElement) {
        this.methodElement = methodElement;
    }
}

2.4. BindView 注解

2.4.1. 获取 BindView 绑定的所有的控件

Set<? extends Element> viewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);

2.4.2. 循环得到每个节点的控件名

for (Element viewElement : viewElements) {

//  遍历所有的成员变量节点      获取每一个成员变量的节点
    VariableElement variableElement = (VariableElement) viewElement;
}

2.4.3. 得到获取节点的父节点,因为是绑定的控件,也就是成员变量,所以父节点就是 Activity

TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();

2.4.4. 得到父节点下的容器中所有的注解

ElementForType elementForType = map.get(typeElement);

2.4.5. 从容器中拿到成员变量控件的集合,如果存在,直接获取,如果不存在,新建一个集合,并放入所有节点的 map 中

// key: activity  value: 注解容器
Map<TypeElement, ElementForType> map = new HashMap<>();
List<VariableElement> variableElements;
if (elementForType != null) {
// 获取 MainActivity 下边的所有的通过 BindView 绑定的成员变量的节点
    variableElements = elementForType.getViewElement();
// 如果不存在 BindView 注解
    if (variableElements == null) {
        variableElements = new ArrayList<>();
        elementForType.setViewElement(variableElements);
    }
} else {
//  如果为空  就创建
    elementForType = new ElementForType();
    variableElements = new ArrayList<>();
    elementForType.setViewElement(variableElements);
    if (!map.containsKey(typeElement)) {
        map.put(typeElement, elementForType);
    }
}

2.4.6. 将获取的节点放入到容器的结合中,和 Activity 形成一一对应的关系

variableElements.add(variableElement);

2.5 OnClick 同样的处理

//        获取应用中 OnClick 标记的方法节点
        Set<? extends Element> methodElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
        for (Element methodElement : methodElements) {
            ExecutableElement executableElement = (ExecutableElement) methodElement;
            TypeElement typeElement = (TypeElement) methodElement.getEnclosingElement();
//            获取容器
            ElementForType elementForType = map.get(typeElement);
            List<ExecutableElement> executableElements;
            if (elementForType != null) {
                executableElements = elementForType.getMethodElement();
//                如果不存在 OnClick 注解
                if (executableElements == null) {
                    executableElements = new ArrayList<>();
                    elementForType.setMethodElement(executableElements);
                }
            } else {
                elementForType = new ElementForType();
                executableElements = new ArrayList<>();
                elementForType.setMethodElement(executableElements);
                if (!map.containsKey(typeElement)) {
                    map.put(typeElement, elementForType);
                }
            }
            executableElements.add(executableElement);
        }

2.6. 写文件

2.6.1. 初始化

    /*用来创建文件的*/
    private Filer filer;

    /**
     * 初始化的方法
     *
     * @param processingEnv 工具类
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

2.6.2. 创建 Activity_ViewHolder,实际上就是按照类的格式一样手写一个类,包含包名,导包,类名,构造等

        if (!map.isEmpty()) {
            Iterator<TypeElement> iterator = map.keySet().iterator();
            Writer writer = null;
            while (iterator.hasNext()) {
//                得到类节点
                TypeElement typeElement = iterator.next();
//                得到容器
                ElementForType elementForType = map.get(typeElement);
//                获取类名
                String className = typeElement.getSimpleName().toString();
//                获取包名
                String packageName = getPackageName(typeElement);
//                创建一个新的类名
                String newClazzName = className + "_ViewHolder";
//                创建文件
                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + newClazzName);
//                    写内容
                    writer = sourceFile.openWriter();
                    StringBuffer sb = getStringBuffer(packageName, newClazzName, typeElement, elementForType);
                    writer.write(sb.toString());

                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if(writer != null) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
            private StringBuffer getStringBuffer(String packageName, String newClazzName, TypeElement typeElement, ElementForType elementForType) {
        StringBuffer sb = new StringBuffer();
        sb.append("package " + packageName + ";\n");
        sb.append("import android.view.View;\n");
        sb.append("public class " + newClazzName + "{\n");
//        构造  参数(包名+类名  target)
        sb.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");

        /**
         * @// TODO: BindView 注解
         */
//        @BindView(R.id.text)
//        TextView textView;
        if (elementForType != null && elementForType.getViewElement() != null && !elementForType.getViewElement().isEmpty()) {
            List<VariableElement> viewElements = elementForType.getViewElement();
            for (VariableElement viewElement : viewElements) {
//                获取类型,是 TextView 还是 Button
                TypeMirror typeMirror = viewElement.asType();
//                获取控件名
                Name simpleName = viewElement.getSimpleName();
//                拿到资源ID (BindView 注解括号中的 ID, 就是 text)
                int resId = viewElement.getAnnotation(BindView.class).value();
//                MainActivity.textView = (TextView)MainActivity.findViewById(R.id.text);
                sb.append("target." + simpleName + " = (" + typeMirror + ")target.findViewById(" + resId + ");\n");
            }
        }

        /**
         * @// TODO: OnClick 注解
         */
//        textView.setOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View view) {
//                  executableElement(view);
//            }
//        });
        if(elementForType != null && elementForType.getMethodElement() != null && !elementForType.getMethodElement().isEmpty()) {
            List<ExecutableElement> methodElement = elementForType.getMethodElement();
            for (ExecutableElement executableElement : methodElement) {
                int[] resIds = executableElement.getAnnotation(OnClick.class).value();
//                获取到 OnClick 注解的方法名
                String methodName = executableElement.getSimpleName().toString();
                for (int resId : resIds) {
                    sb.append("target.findViewById(" + resId + ").setOnClickListener(new View.OnClickListener() {\n");
                    sb.append("public void onClick(View view) {\n");
                    sb.append("target." + methodName + "(view);\n");
                    sb.append("}\n});\n");
                }
            }
        }

        sb.append("}\n}\n");
        return sb;
    }

2.7. 生成的 ViewHolder 格式如下

package com.dome.butter;

import android.view.View;

public class MainActivity_ViewHolder {
    public MainActivity_ViewHolder(final com.dome.butter.MainActivity target) {
        target.textView = (android.widget.TextView) target.findViewById(2131230980);
        target.findViewById(2131230980).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                target.onClick(view);
            }
        });
    }
}

2.8. 如何使用

public class ButterKnife {

    public static void bind(Object clazz) {
        String name = clazz.getClass().getName();
        String bindName = name + "_ViewHolder";
        try {
//            获取类
            Class<?> aClass = Class.forName(bindName);
//            获取构造
            Constructor<?> constructor = aClass.getConstructor(clazz.getClass());
//            使用构造
            constructor.newInstance(clazz);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.9. 至此,一个基本的 ButterKnife 完成,下边贴出构建起完成代码

// 注册这个类是注解处理器
@AutoService(Processor.class)
// 声明需要处理的注解有哪些
@SupportedAnnotationTypes({"com.dome.annotation.BindView", "com.dome.annotation.OnClick"})
// 声明需要支持的 Java 版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationCompiler extends AbstractProcessor {

    /*用来创建文件的*/
    private Filer filer;

    /**
     * 初始化的方法
     *
     * @param processingEnv 工具类
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

    /**
     * 注解处理器的核心方法
     * <p/> 所有要做的业务逻辑处理  都在这个方法中
     *
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//        将所有的节点  都以类的方式进行一一对应
        Map<TypeElement, ElementForType> map = findAndParserTarget(roundEnvironment);
        if (!map.isEmpty()) {
            Iterator<TypeElement> iterator = map.keySet().iterator();
            Writer writer = null;
            while (iterator.hasNext()) {
//                得到类节点
                TypeElement typeElement = iterator.next();
//                得到容器
                ElementForType elementForType = map.get(typeElement);

                /**
                 * @// TODO: 开始处理文件
                 */
//                获取类名
//                全路径,存在包名
//                typeElement.getQualifiedName();
//                只有名字,没有包名
                String className = typeElement.getSimpleName().toString();
//                获取包名
                String packageName = getPackageName(typeElement);
//                创建一个新的类名
                String newClazzName = className + "_ViewHolder";
//                创建文件
                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + newClazzName);
//                    写内容
                    writer = sourceFile.openWriter();
                    StringBuffer sb = getStringBuffer(packageName, newClazzName, typeElement, elementForType);
                    writer.write(sb.toString());

                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if(writer != null) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * 类和注解一一对应
     *
     * @param roundEnvironment
     * @return
     * @// TODO: 2024/1/4  Map<TypeElement, ElementForType>
     * TypeElement: 类     ElementForType: 当前类中所有的注解,目前声明了成员变量和方法
     */
    private Map<TypeElement, ElementForType> findAndParserTarget(RoundEnvironment roundEnvironment) {
//        key: activity  value: 注解容器
        Map<TypeElement, ElementForType> map = new HashMap<>();

//        BindView 注解处理
        getViewAnnotation(map, roundEnvironment);
//        OnClick 注解处理
        getMethodAnnotation(map, roundEnvironment);

        return map;
    }

    /**
     * BindView 注解处理
     *
     * @// TODO: 以下代码以  MainActivity 说明
     */
    public void getViewAnnotation(Map<TypeElement, ElementForType> map, RoundEnvironment roundEnvironment) {
//        获取应用中 BindView 标记的节点   包含 MainActivity 和 TwoActivity 通过 BindView 的控件
        Set<? extends Element> viewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for (Element viewElement : viewElements) {
            /**
             * @// TODO: 获取到绑定的节点的注解
             */
//            遍历所有的成员变量节点      获取每一个成员变量的节点
            VariableElement variableElement = (VariableElement) viewElement;

            /**
             * @// TODO: 得到父节点
             */
//            获取到成员变量的类节点   getEnclosingElement()可以获取当前节点的上一个节点
//            variableElement(成员变量)的节点的上一个节点就是类节点
//            找到 MainActivity 的节点
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();

            /**
             * @// TODO: 得到父节点注解的容器 ElementForType(ElementForType 包含成员变量和方法注解)
             */
//            获取 MainActivity 节点中的值
            ElementForType elementForType = map.get(typeElement);

            /**
             * @// TODO: 获取对应的集合
             */
            List<VariableElement> variableElements;
            if (elementForType != null) {
//                获取 MainActivity 下边的所有的通过 BindView 绑定的成员变量的节点
                variableElements = elementForType.getViewElement();
//                如果不存在 BindView 注解
                if (variableElements == null) {
                    variableElements = new ArrayList<>();
                    elementForType.setViewElement(variableElements);
                }
            } else {
//                如果为空  就创建
                elementForType = new ElementForType();
                variableElements = new ArrayList<>();
                elementForType.setViewElement(variableElements);
                if (!map.containsKey(typeElement)) {
                    map.put(typeElement, elementForType);
                }
            }

            /**
             * @// TODO: 给集合添加数据
             */
//            将拿到的控件注解放入到对应的集合中
            variableElements.add(variableElement);
        }
    }

    /**
     * OnClick 注解处理
     */
    public void getMethodAnnotation(Map<TypeElement, ElementForType> map, RoundEnvironment roundEnvironment) {
//        获取应用中 OnClick 标记的方法节点
        Set<? extends Element> methodElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
        for (Element methodElement : methodElements) {
            ExecutableElement executableElement = (ExecutableElement) methodElement;
            TypeElement typeElement = (TypeElement) methodElement.getEnclosingElement();
//            获取容器
            ElementForType elementForType = map.get(typeElement);
            List<ExecutableElement> executableElements;
            if (elementForType != null) {
                executableElements = elementForType.getMethodElement();
//                如果不存在 OnClick 注解
                if (executableElements == null) {
                    executableElements = new ArrayList<>();
                    elementForType.setMethodElement(executableElements);
                }
            } else {
                elementForType = new ElementForType();
                executableElements = new ArrayList<>();
                elementForType.setMethodElement(executableElements);
                if (!map.containsKey(typeElement)) {
                    map.put(typeElement, elementForType);
                }
            }
            executableElements.add(executableElement);
        }
    }

    /**
     * 写文件   拼装生成文件的代码
     *
     * @param packageName
     * @param newClazzName
     * @param typeElement
     * @param elementForType
     * @return
     */
    private StringBuffer getStringBuffer(String packageName, String newClazzName, TypeElement typeElement, ElementForType elementForType) {
        StringBuffer sb = new StringBuffer();
        sb.append("package " + packageName + ";\n");
        sb.append("import android.view.View;\n");
        sb.append("public class " + newClazzName + "{\n");
//        构造  参数(包名+类名  target)
        sb.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");

        /**
         * @// TODO: BindView 注解
         */
//        @BindView(R.id.text)
//        TextView textView;
        if (elementForType != null && elementForType.getViewElement() != null && !elementForType.getViewElement().isEmpty()) {
            List<VariableElement> viewElements = elementForType.getViewElement();
            for (VariableElement viewElement : viewElements) {
//                获取类型,是 TextView 还是 Button
                TypeMirror typeMirror = viewElement.asType();
//                获取控件名
                Name simpleName = viewElement.getSimpleName();
//                拿到资源ID (BindView 注解括号中的 ID, 就是 text)
                int resId = viewElement.getAnnotation(BindView.class).value();
//                MainActivity.textView = (TextView)MainActivity.findViewById(R.id.text);
                sb.append("target." + simpleName + " = (" + typeMirror + ")target.findViewById(" + resId + ");\n");
            }
        }

        /**
         * @// TODO: OnClick 注解
         */
//        textView.setOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View view) {
//                  executableElement(view);
//            }
//        });
        if(elementForType != null && elementForType.getMethodElement() != null && !elementForType.getMethodElement().isEmpty()) {
            List<ExecutableElement> methodElement = elementForType.getMethodElement();
            for (ExecutableElement executableElement : methodElement) {
                int[] resIds = executableElement.getAnnotation(OnClick.class).value();
//                获取到 OnClick 注解的方法名
                String methodName = executableElement.getSimpleName().toString();
                for (int resId : resIds) {
                    sb.append("target.findViewById(" + resId + ").setOnClickListener(new View.OnClickListener() {\n");
                    sb.append("public void onClick(View view) {\n");
                    sb.append("target." + methodName + "(view);\n");
                    sb.append("}\n});\n");
                }
            }
        }

        sb.append("}\n}\n");
        return sb;
    }

    /**
     * 获取包名
     *
     * @param typeElement
     * @return
     */
    private String getPackageName(Element typeElement) {
        PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(typeElement);
        Name name = packageOf.getQualifiedName();
        return name.toString();
    }

    /**
     * 打印日志
     */
    public void logUtil(String log) {
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, log);
    }
}

3. Element 类型

TypeElement(类型元素):表示一个类或接口元素。TypeElement代表类、接口、枚举或注解类型。 VariableElement(变量元素):表示一个字段、enum常量、方法或构造方法的参数、局部变量或异常参数。 (ExecutableElement(可执行元素):表示一个方法、构造方法或初始化程序(静态或实例)。 PackageElement(包元素): 表示一个包程序元素。 Parameterizable(可参数化的元素):代表可以具有类型参数的元素,例如泛型类、接口、方法或构造方法。 TypeParameterElement(类型参数元素): 表示一个类型参数。 UnknownElement:未知元素类型。在某些情况下,可能会遇到无法识别的元素。