美文网首页
ButterKnife详解与原理分析

ButterKnife详解与原理分析

作者: 佼佼者Mr | 来源:发表于2020-03-26 23:31 被阅读0次

什么是ButterKnife?

俗称黄油刀,是控件注入框架,可以帮助开发者避免重复初始化控件的工作,简单快捷的初始化布局中的控件,极大提升开发效率。

优势:

强大的view和click事件处理能力,简化代码,提升开发效率

方便的处理adapter里面viewHolder绑定事件

运行时不会影响app开发效率,使用配置方便

代码清晰可读性强

缺点:生成class文件造成apk体积变大

原理:利用apt技术,生成class文件,在class文件里面绑定view,处理click事件

这是生成的一个文件

//public class MainActivity_ViewBinding {

//    private MainActivity target;

//    private View view7f070080;

//

//    private View view7f070081;

//

//    @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.tv1, "field 'tv1' and method 'click'");

//        target.tv1 = Utils.castView(view, R.id.tv1, "field 'tv1'", TextView.class);

//        view7f070080 = view;

//        view.setOnClickListener(new DebouncingOnClickListener() {

//            @Override

//            public void doClick(View p0) {

//                target.click(p0);

//            }

//        });

//        view = Utils.findRequiredView(source, R.id.tv2, "field 'tv2' and method 'click2'");

//        target.tv2 = Utils.castView(view, R.id.tv2, "field 'tv2'", TextView.class);

//        view7f070081 = view;

//        view.setOnClickListener(new DebouncingOnClickListener() {

//            @Override

//            public void doClick(View p0) {

//                target.click2();

//            }

//        });

//        target.tv3 = Utils.findRequiredViewAsType(source, R.id.tv3, "field 'tv3'", TextView.class);

//        target.tv4 = Utils.findRequiredViewAsType(source, R.id.tv4, "field 'tv4'", TextView.class);

//    }

//

//    @Override

//    @CallSuper

//    public void unbind() {

//        MainActivity target = this.target;

//        if (target == null) throw new IllegalStateException("Bindings already cleared.");

//        this.target = null;

//

//        target.tv1 = null;

//        target.tv2 = null;

//        target.tv3 = null;

//        target.tv4 = null;

//

//        view7f070080.setOnClickListener(null);

//        view7f070080 = null;

//        view7f070081.setOnClickListener(null);

//        view7f070081 = null;

//    }

想要理解ButterKnife或者自己手写就要了解Java结构化语言

类元素、包元素、方法元素、属性元素

Element 

PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问

ExecutableElement 表示某个类或接口的方法,构造方法或初始化程序(静态或实例)

TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问

VariableElement 表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数

利用JavaPoat技术手写的ButterKnife生成class文件的process类,其他流程同ButterKnife一样,可参考ButterKnife作者

// 用来生成 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);

                    }

                }

            }

        }

    }

}

相关文章

  • ButterKnife详解与原理分析

    什么是ButterKnife? 俗称黄油刀,是控件注入框架,可以帮助开发者避免重复初始化控件的工作,简单快捷的初始...

  • ButterKnife源码分析

    目的:分析ButterKnife如何进行view与onClick事件的绑定 原理分析 通过观察BindView注解...

  • ButterKnife原理分析

    ButterKnife源码地址 ButterKnife的分析文章很多了,这里只是简单分析原理,不想看代码,可以直接...

  • ButterKnife原理与源码分析

    参考深入理解ButterKnife源码并掌握原理

  • ButterKnife原理分析

    官方传送门 本文基于compile 'com.jakewharton:butterknife:8.6.0'分析 1...

  • ButterKnife原理分析

    前言 ButterKnife又名黄油刀,是一款知名的Andorid框架,通过注解绑定,省去初始化控件等重复工作,简...

  • ButterKnife(二): 原理解析篇

    在上一篇文章ButterKnife使用篇里面已经详细介绍过如何使用ButterKnife了,本篇文章将分析它的原理...

  • Mysql性能调优

    SQL执行原理详解 连接器详解 分析器详解 优化器详解 执行器详解 Innodb的Buffer Pool机制详解 ...

  • 开源框架_02ButterKnife

    参考文章 : ButterKnife使用和原理 深入理解ButterKnife源码并掌握原理(一) 深入理解But...

  • Android常用之Butterknife使用详解

    Android常用之Butterknife使用详解 使用Butterknife很久了,一直没有写过文章,今天记录一...

网友评论

      本文标题:ButterKnife详解与原理分析

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