美文网首页
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