美文网首页
butterknife 分析和手写

butterknife 分析和手写

作者: 飘絮无意 | 来源:发表于2020-04-22 14:44 被阅读0次

    ButterKnife优势
    强大的View绑定和Click事件处理功能,简化繁琐的代码编写
    可以支持Adapter中的VIewHolder绑定问题
    采用编译时通过注解生成代码,对运行时没有侵入,对比反射方式,效率倍高
    代码清晰,可读性强
    ButterKnife使用
    在android module的build.gradle配置文件中,引入如下依赖 apt 已经被annotationProcessor 取代了 不要忘了了加

    dependencies {
        /* butterknife */
        implementation 'com.jakewharton:butterknife:10.2.1'
        annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
    }
    

    ButterKnife使用实例
    a 注入视图

        @BindView(R.id.toolbar)
        Toolbar toolbar;
    
        @BindView(R.id.fab)
        FloatingActionButton fab;
    

    b 注入事件

    @OnClick(R.id.fab)
    public void show(View view){
            Snackbar.make(view, "Replace with your own action",Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
    }
    

    <meta charset="utf-8">

    ButterKnife原理分析

    可能很多人都觉得ButterKnife在bind(this)方法执行的时候通过反射获取MainActivity中所有的带有@BindView注解的属性并且获得注解中的R.id.xxx值,最后还是通过反射拿到Activity.findViewById()方法获取View,并赋值给MainActivity中的某个属性。这是一种原始的使用反射的方式,缺点是反射影响App性能,造成卡顿,并且会产生大量的临时对象,频繁的引发GC。

    ButterKnife显然没有使用这种方式,它用了Java Annotation Processing技术,就是在Java代码编译成Java字节码的时候就已经处理了@Bind、@OnClick(ButterKnife还支持很多其他的注解)这些注解了。

    Java Annotation Processing是java中用于编译时扫描和解析Java注解的工具 也就是我们说到的apt技术

    你可以你定义注解,并且自己定义解析器来处理它们。Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法
    下图是Java 编译代码的整个过程,可以帮助我们很好理解注解解析的过程:


    image

    ButterKnife工作原理(知识点!)
    当你编译使用了ButterKnife框架的应用程序时,ButterKnifeProcessor类的process()方法开始工作,会执行以下操作:

    ①.编译期间通过反射扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等
    ②.当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似<className>$$ViewBinder,这个新生成的类实现了ViewBinder<T>接口
    ③.这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等
    ④.最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法

    原理分析介绍到这里 看是手写一个吧
    手写ButterKnife
    可以去 github 上下载ButterKnife 的源码来看,我就直接仿着写定义三个lib 两个 java lib 一个 android lib

    image.png
    butterknife 为 android lib 主要存放反射生成 xxx__ViewBinding 方法和对应 findViewById 方法
    butterknife_annotations 为 java lib 主要存放注解类型
    butterknife_compiler 为 java lib 主要存放编译时自动生成类的类和方法
    先来看butterknife_annotations 这个 lib
     //定义ViewBind 注解,用于findViewById
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    public @interface ViewBind {
        int value();
    }
    
     //定义OnViewClick 注解,用于绑定view 的监听事件
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface OnViewClick {
        int[] value();
    }
    

    再来看 butterknife_compiler 记得加上annotationProcessor 不然成功不了

    dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    //  //引入 butterknife_annotations 使用里面定义的注解
    implementation project(':butterknife_annotations')
    //下面这两个是专门用来编译生成类的
    implementation 'com.squareup:javapoet:1.10.0'
    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    implementation files('build/libs/butterknife_compiler.jar')
    

    }
    新建 ButterKnifeProcessor

    //注意增加 AutoService 注解
    @AutoService(Processor.class)
    public class ButterKnifeProcessor extends AbstractProcessor {
        //重写方法,返回版本号
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
        
        //获取Filer 和 ElementUtils
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            mFiler = processingEnvironment.getFiler();
            mElementUtils = processingEnvironment.getElementUtils();
        }
    
        //添加注解的类型
        //仿照 ButterKnife 源码写的
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
                types.add(annotation.getCanonicalName());
            }
            return types;
        }
    
        private Set<Class<? extends Annotation>> getSupportedAnnotations() {
            Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
            annotations.add(ViewBind.class);
            annotations.add(OnViewClick.class);
            return annotations;
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //编译时,就会走这个方法里面
            //测试是否走到这里面
            System.out.println(">>>>>>>>>>>>>>>>>");
        }
    }
    
    

    现在接着 app 工程下增加依赖

    dependencies {
        //依赖三个我们新建的lib
        implementation project(':butterknife')
        implementation project(':butterknife_annotations')
        //注意,这里使用annotationProcessor 这是新版本 apt 的使用方式
        annotationProcessor project(':butterknife_compiler')
    }
    

    依赖完成之后,rebuild项目,在build下查看是否有打印


    image.png

    在这里面如果能打印日志出来,说明配置没有问题,继续走下一步,否则,需要查看配置是不是有问题
    重点来了,接下来来编写 process 方法,我直接上代码,

    @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
           System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            //获取 ViewBind 的element
            Set<? extends Element> viewBindElement = roundEnv.getElementsAnnotatedWith(ViewBind.class);
           //获取 onViewClick的element
            Set<? extends Element> clickdElement = roundEnv.getElementsAnnotatedWith(onViewClick.class);
            Map<TypeElement, List<BindingSet>> map= new LinkedHashMap<>();
    
            for (Element element : viewBindElement) {
               //注解的变量名
                String simpleName = element.getSimpleName().toString();
                //注解的id
                int value = element.getAnnotation(ViewBind.class).value();
                //对应所在class Element
                TypeElement typeElement= (TypeElement)element.getEnclosingElement();
                //保存进 map 中
                //ElementBindSet 是一个自定义的类,里面保存有 Element 和 type 字段
                //用于区分是 ViewBind 还是 OnViewClick
                List<BindingSet> sets=map.get(typeElement);
                if (sets==null){
                    sets=new ArrayList<>();
                    map.put(typeElement,sets);
                }
                sets.add(new BindingSet(element,0));
            }
    
            //遍历clickdElement
            for (Element element : clickdElement) {
              TypeElement elementType= (TypeElement) element.getEnclosingElement();
                List<BindingSet> setList = map.get(elementType);
                if (setList==null){
                    setList=new ArrayList<>();
                    map.put(elementType,setList);
                }
                setList.add(new BindingSet(element,1));
            }
    
            //遍历 map 有几个 TypeElement 说明要创建几个类
            for (TypeElement typeElement : map.keySet()) {
                //获取包名
                String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();
                //获取类名
                String className = typeElement.getSimpleName().toString();
                //通过 ClassName 来获取 定义的 Unbinder 接口
                ClassName unbinder = ClassName.get("com.example.shengdian.butterknife", "Unbinder");
                //通过 ClassName 获取类
                ClassName target = ClassName.get(packageName, className);
                //创建 unbind 方法,在里面编写代码
                MethodSpec.Builder unbind_builder = MethodSpec.methodBuilder("unbind")
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.PUBLIC)
                        .returns(TypeName.VOID)
                        .addStatement("$N target = this.target", className)
                        .addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")")
                        .addStatement("this.target = null");
                //创建构造方法
                MethodSpec.Builder construction_builder = MethodSpec.constructorBuilder()
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(target, "target", Modifier.FINAL)
                        .addStatement("this.$N = $N", "target", "target");
                //得到对应 TypeElement 中的 ElementBindSet
                List<BindingSet> elementList = map.get(typeElement);
                //通过 ClassName 获取 com.butterknife.Utils
                //Utils 是自己定义的工具类,用来做 findViewById 操作
                ClassName utils = ClassName.get("com.example.shengdian.butterknife", "Utils");
                //通过 ClassName 获取 view
                ClassName view = ClassName.get("android.view", "View");
                //获取 DebouncingOnClickListener
                //DebouncingOnClickListener 是一个实现点击事件接口的抽象类
                ClassName debouncingOnClickListener = ClassName.get("com.example.shengdian.butterknife", "DebouncingOnClickListener");
                //新建对应 xxx_ViewBinding 的  TypeSpec.Builder 用于生成类代码
                TypeSpec.Builder bindingBuilder = TypeSpec.classBuilder(className + "_ViewBinding")
                        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                        .addSuperinterface(unbinder);
                //判断是否包含 OnViewClick
                if (checkHaveClick(elementList)) {
                    //如果包含,就在构造方法中增加 View view 代码
                    construction_builder.addStatement("$N view", view.toString());
                }
                //遍历所有 ElementBindSet 即所有的注解
                for (BindingSet element : elementList) {
                    //如果为 ViewBind
                    if (element.type == 0) {
                        //获取该注解的 参数名
                        String simpleName = element.mElement.getSimpleName().toString();
                        //获取注解 ID 值
                        int id = element.mElement.getAnnotation(ViewBind.class).value();
                        //在构造方法中添加代码
                        // target.参数名 = Utils.findViewById(target,id);
                        construction_builder.addStatement("target.$N = $N.findViewById(target, $N)",
                                simpleName, utils.toString(), String.valueOf(id));
                        //在 unbind 方法中添加代码
                        //target.参数名 = null
                        unbind_builder.addStatement("target.$N = null", simpleName);
                    }
                    //如果为 OnViewClick
                    if (element.type == 1) {
                        //获取注解 ID 所有值
                        int[] onClickIds = element.mElement.getAnnotation(onViewClick.class).value();
                        for (int onClickId : onClickIds) {
                            bindingBuilder.addField(view, "view" + onClickId);
                            construction_builder.addStatement("view = $N.findViewById(target, $N)", utils.toString(), String.valueOf(onClickId));
                            construction_builder.addStatement("view" + onClickId + "= view");
                            //新增监听点击的代码
                            MethodSpec.Builder onClickBuilder = MethodSpec.methodBuilder("doClick")
                                    .addAnnotation(Override.class)
                                    .addModifiers(Modifier.PUBLIC)
                                    .returns(TypeName.VOID)
                                    .addParameter(view, "p0")
                                    .addStatement("target.$N(p0)", element.mElement.getSimpleName());
                            TypeSpec onClick = TypeSpec.anonymousClassBuilder("")
                                    .superclass(debouncingOnClickListener)
                                    .addMethod(onClickBuilder.build())
                                    .build();
                            construction_builder.addStatement("view.setOnClickListener($N)", onClick.toString());
                            //在 unbind 方法中清空
                            unbind_builder.addStatement("view" + onClickId + ".setOnClickListener(null)");
                            unbind_builder.addStatement("view" + onClickId + "=null");
                        }
                    }
                }
                //在类中添加对应的方法和构造方法
                bindingBuilder.addMethod(unbind_builder.build())
                        .addMethod(construction_builder.build())
                        .addField(target, "target");
                try {
                    //同噶javaFile生成对应的类
                    JavaFile javaFile = JavaFile.builder(packageName, bindingBuilder.build()).build();
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    
           private boolean checkHaveClick(List<BindingSet> elementList) {
               for (BindingSet elementBindSet : elementList) {
                      if (elementBindSet.getType()==1){
                          return true;
                      }
               }
                return false;
           }
    
    public class BindingSet implements Serializable {
    
        Element mElement;
        int  type;
    
        public BindingSet(Element mElement, int type) {
            this.mElement = mElement;
            this.type = type;
        }
    
        public Element getmElement() {
            return mElement;
        }
    
        public void setmElement(Element mElement) {
            this.mElement = mElement;
        }
    
        public int getType() {
            return type;
        }
    
        public void setType(int type) {
            this.type = type;
        }
    }
    

    下面来完善butterknife这个lib 的内容,其实就是增加几个方法
    新增 Butter 类、Unbinder 以及DebouncingOnClickListener 和Utils

    ublic final class Butter {
        static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
    
        @NonNull
        @UiThread
        public static Unbinder bind(@NonNull Activity target) {
            Class<?> aClass = target.getClass();
            Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(aClass);
            if (constructor == null) {
                return Unbinder.EMPTY;
            }
            try {
                return constructor.newInstance(target);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                }
                if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException("Unable to create binding instance.", cause);
            }
            return Unbinder.EMPTY;
        }
    
        private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
            Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
            if (bindingCtor != null || BINDINGS.containsKey(cls)) {
                return bindingCtor;
            }
            String clsName = cls.getName();
            if (clsName.startsWith("android.") || clsName.startsWith("java.")
                    || clsName.startsWith("androidx.")) {
                return null;
            }
            try {
                Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
                //需要注意的是,我们定义的只有一个参数的构造函数,所以传一个参数 可以多个
                bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls);
            } catch (ClassNotFoundException e) {
                bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
            }
            BINDINGS.put(cls, bindingCtor);
            return bindingCtor;
        }
    }
      //定义 Unbinder 接口,照抄
        public interface Unbinder {
        void unbind();
    
        Unbinder EMPTY = new Unbinder() {
            @Override
            public void unbind() {
    
            }
        };
    }
       
        //定义 DebouncingOnClickListener 用于点击事件,照抄
        public abstract class DebouncingOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            doClick(v);
        }
    
        public abstract void doClick(View v);
    }
    
        //utils 就是做了 findViewById
        public class Utils {
        public static <T extends View> T findViewById(Activity activity, int id) {
            return activity.findViewById(id);
        }
    }
    

    大功告成,换上我们自己的注解

    image.png
    最后得感谢 这篇文章

    相关文章

      网友评论

          本文标题:butterknife 分析和手写

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