美文网首页
2019-03-14 ButterKnife 源码解析和手写

2019-03-14 ButterKnife 源码解析和手写

作者: 猫KK | 来源:发表于2019-03-14 15:53 被阅读0次

    ButterKnife 使用

    引入 ButterKnife 可以去这里查看最新的版本,我使用的是8.8.0版本,10.0.0版本有兼容问题,没有使用,但是源码基本一样

    //gradle 文件中
    dependencies {
        implementation 'com.jakewharton:butterknife:8.8.0'
        annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.0'
    }
    

    使用:

    public class Main2Activity extends AppCompatActivity {
    
         //绑定控件,不用再写 findViewById
        @BindView(R.id.tv_test)
        TextView mTv_1;
        @BindView(R.id.tv_test1)
        TextView mTv_2;
        private Unbinder mBind;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
            //绑定对应activity
            mBind = ButterKnife.bind(this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //新版本的ButterKnife 解注册,防止内存泄露
            if (mBind != null) {
                mBind.unbind();
            }
        }
    
        //绑定点击事件
        @OnClick({R.id.tv_test1, R.id.tv_test})
        void onClick(View view) {
    
        }
    }
    

    除了上面的绑定控件、点击事件,还可以绑定 String 、Animation 等一系列

    分析:@BindView、@OnClick 是自定义的注解,并且为编译时注解

    //CLASS 表示为编译时的注解
    @Retention(CLASS) 
    //FIELD 表示为变量注解
    @Target(FIELD)
    public @interface BindView {
      /** View ID to which the field will be bound. */
      @IdRes int value();
    }
    
    // METHOD 表示为方法注解
    @Target(METHOD)
    //CLASS 表示为编译时的注解
    @Retention(CLASS)
    @ListenerClass(
        targetType = "android.view.View",
        setter = "setOnClickListener",
        type = "butterknife.internal.DebouncingOnClickListener",
        method = @ListenerMethod(
            name = "doClick",
            parameters = "android.view.View"
        )
    )
    public @interface OnClick {
      /** View IDs to which the method will be bound. */
      @IdRes int[] value() default { View.NO_ID };
    }
    

    只是定义了注解,什么都没做。继续看 ButterKnife.bind(this);

        public static Unbinder bind(@NonNull Activity target) {
        //获取DecorView
        View sourceView = target.getWindow().getDecorView();
        return createBinding(target, sourceView);
      }
    
        private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
        //获取Class
        Class<?> targetClass = target.getClass();
        if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
        //获取构造方法
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    
        if (constructor == null) {
          return Unbinder.EMPTY;
        }
    
        //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
        try {
          //通过构造方法反射创建 class
          //所以ButterKnife 也使用了反射
          return constructor.newInstance(target, source);
        } catch (IllegalAccessException e) {
          throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InstantiationException e) {
          throw new RuntimeException("Unable to invoke " + constructor, e);
        } 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);
        }
      }
    
        private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        //通过缓存获取
        //BINDINGS 是一个静态的LinkedHashMap
        //使用缓存提高性能
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        //判断缓存是否存在,存在就直接返回
        if (bindingCtor != null) {
          if (debug) Log.d(TAG, "HIT: Cached in binding map.");
          return bindingCtor;
        }
        //判断 cls 名称,是否可以使用
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
          if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
          return null;
        }
        try {
           //通过ClassLoader 获取对应的Class
          //注意该 Class为 xxx_ViewBinding,按照事例为 Main2Activity_ViewBinding
          Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
          //noinspection unchecked
          //通过 Class 获取构造方法
          bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
          if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
        } catch (ClassNotFoundException e) {
          if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
          bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
        } catch (NoSuchMethodException e) {
          throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
        }
        //放入缓存
        BINDINGS.put(cls, bindingCtor);
        //返回
        return bindingCtor;
      }
    

    所以上面的代码为:通过反射创建了一个 Main2Activity_ViewBinding 对象,并将当前 activity 实例传进去

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
            mBind = ButterKnife.bind(this);
            //ButterKnife.bind(this) 就相当于 new 一个对象
            mBind = new Main2Activity_ViewBinding(this);
        }
    

    等等,Main2Activity_ViewBinding 在哪里,都没有新建过这个类,这个类是在 build/generatd/source/apt/debug/对应报名 下,看做了什么

    public class Main2Activity_ViewBinding implements Unbinder {
      private Main2Activity target;
    
      private View view2131165338;
    
      private View view2131165339;
    
      @UiThread
      public Main2Activity_ViewBinding(final Main2Activity target, View source) {
        this.target = target;
    
        View view;
        //Utils.findRequiredView 其实就是调用了 findViewById 方法
        view = Utils.findRequiredView(source, R.id.tv_test, "field 'mTv_1' and method 'onClick'");
        target.mTv_1 = Utils.castView(view, R.id.tv_test, "field 'mTv_1'", TextView.class);
        view2131165338 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.onClick(p0);
          }
        });
        view = Utils.findRequiredView(source, R.id.tv_test1, "field 'mTv_2' and method 'onClick'");
        target.mTv_2 = Utils.castView(view, R.id.tv_test1, "field 'mTv_2'", TextView.class);
        view2131165339 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.onClick(p0);
          }
        });
      }
    
      @Override
      @CallSuper
      public void unbind() {
        Main2Activity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
    
        target.mTv_1 = null;
        target.mTv_2 = null;
    
        view2131165338.setOnClickListener(null);
        view2131165338 = null;
        view2131165339.setOnClickListener(null);
        view2131165339 = null;
      }
    }
    

    所以其实就是 new Main2Activity_ViewBinding 对象,在构造方法中找到对应的变量做了一遍 findViewById。那么这个 Main2Activity_ViewBinding 类是什么时候创建的呢?通过编译时的注解,在编译的时候通过apt创建出对应的类,下面来模仿手写

    手写ButterKnife

    可以去 github 上下载ButterKnife 的源码来看,我就直接仿着写:定义三个lib 两个 java lib 一个 android lib


    WechatIMG51.jpeg

    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

        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'
    }
    

    新建 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')
        }
    

    依赖完成之后,运行app工程,在build下查看是否有打印


    WechatIMG415.jpeg

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

        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //获取 ViewBind 的Element
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ViewBind.class);
            //获取 OnViewClick Element
            Set<? extends Element> clickElements = roundEnvironment.getElementsAnnotatedWith(OnViewClick.class);
            //新建 Map 用来保存不同类的内容
            //一个 TypeElement 对应一个有注解的 activity
            Map<TypeElement, List<ElementBindSet>> map = new LinkedHashMap<>();
            //遍历 ViewBind 的 Element
            for (Element element : elements) {
                //注解的变量名
                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<ElementBindSet> elementList = map.get(typeElement);
                if (elementList == null) {
                    elementList = new ArrayList<>();
                    map.put(typeElement, elementList);
                }
                ElementBindSet bindSet = new ElementBindSet();
                bindSet.mElement = element;
                bindSet.type = 0;
                elementList.add(bindSet);
            }
            //遍历 OnViewClick
            for (Element clickElement : clickElements) {
                TypeElement typeElement = (TypeElement) clickElement.getEnclosingElement();
                List<ElementBindSet> elementList = map.get(typeElement);
                if (elementList == null) {
                    elementList = new ArrayList<>();
                    map.put(typeElement, elementList);
                }
                ElementBindSet bindSet = new ElementBindSet();
                bindSet.mElement = clickElement;
                bindSet.type = 1;
                elementList.add(bindSet);
            }
            //遍历 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.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<ElementBindSet> elementList = map.get(typeElement);
                //通过 ClassName 获取 com.butterknife.Utils
                //Utils 是自己定义的工具类,用来做 findViewById 操作
                ClassName utils = ClassName.get("com.butterknife", "Utils");
                //通过 ClassName 获取 view
                ClassName view = ClassName.get("android.view", "View");
                //获取 DebouncingOnClickListener
                //DebouncingOnClickListener 是一个实现点击事件接口的抽象类
                ClassName debouncingOnClickListener = ClassName.get("com.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 (ElementBindSet 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;
        }
    

    代码很长,只需要了解语法,跟着 ButterKnife 生成的 xxx _ViewBinding 类来敲,很简单的,下面来完善butterknife这个lib 的内容,其实就是增加几个方法
    新增 Butter 类,用来和 ButterKnife 区别

        public class Butter {
        static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
    
        public static Unbinder bind(Activity activity) {
            Class<?> aClass = activity.getClass();
            Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(aClass);
            if (constructor == null) {
                return Unbinder.EMPTY;
            }
            try {
                return constructor.newInstance(activity);
            } 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;
        }
    }
    

    其实就是照抄 ButterKnife 的代码,需要注意的是我们定义的只有一个参数的构造函数,所以传一个参数来获取Constructor,然后就是剩下需要的类

        //定义 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);
        }
    }
    

    大功告成,最后来修改前面的 Main2Activity ,换上我们自己的注解

    public class Main2Activity extends AppCompatActivity {
    
        @ViewBind(R.id.tv_test)
        TextView mTv_1;
        @ViewBind(R.id.tv_test1)
        TextView mTv_2;
        private Unbinder mBind;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
    //        mBind = ButterKnife.bind(this);
    //        mBind = new Main2Activity_ViewBinding(this);
            mBind = Butter.bind(this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mBind != null) {
                mBind.unbind();
            }
        }
    
        @OnViewClick({R.id.tv_test1, R.id.tv_test})
        void onClick(View view) {
    
        }
    }
    

    完美运行。。。。。。

    相关文章

      网友评论

          本文标题:2019-03-14 ButterKnife 源码解析和手写

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