美文网首页
Butterknife原理

Butterknife原理

作者: 旺仔_100 | 来源:发表于2021-07-04 12:08 被阅读0次

    一、目标

    写一个demo来实现Butterknife的findViewById功能。

    二、核心原理以及实现

    核心原理

    通过@BindView注解来替代findViewById。主要是通过APT(注解解析器)在编译文件的时候查找到@BindView注解,获取到对应的控件引用和控件id,动态的生成java文件,里面会有个bindView方法,方法内容就是activity.findViewById(id)。java文件可以通过两种方式,一种是直接使用StringBuilder拼接,还有一种是通过javapoet的api来拼接。后者不会出现拼写错误。最后一步就是反射调用生成的bindview方法。

    实现

    项目主要分为四个模块

    • app 主要是用来测试我们功能实现的。
    • apt-annotation 是 一个java library,主要放置自定义注解@SensorsDataBindView
    • apt-processor 是我们注解处理器模块,它主要是根据apt-annotation模块中定义的注解,在编译时生成xxxActivity_sensorsDataViewBinding.java文件。该module需要依赖apt-annotation。
    • apt-sdk 是一个android module,它反射调用apt-process模块生成的xxxActivity_sensorsDataViewBinding.java中的方法,实现对View的绑定,该module需要依赖apt-annotation。

    三、代码实现及对应讲解

    a、创建一个java library apt-annotation

    自定义注解@SensorsDataBindView

    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.FIELD)
    public @interface SensorsDataBindView {
        int value();
    }
    

    上述代码使用元注解@Retention和@Target
    @Retention 定义该注解被保留的时间长短策略。

    • SOURCE 表示会被编译器忽略
    • CLASS 表示该注解将会被保留在Class文件中,但在运行时并不会被VM保留。这是一种默认行为,没有使用@Retention注解的注解都会采用这种策略。
    • RUNTIME 表示保留至运行时。所以我们可以通过反射去获取注解信息。
      @Tartget
      定义注解所修饰的对象范围。Annotation可用于packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量。
      Target主要有下面几种范围:
    • CONSTRUCTOR:用于描述构造器;
    • FIELD:用于描述域;
    • LOCAL_VARIABLE:用于描述局部变量;
    • METHOD:用于描述方法;
    • PACKAGE:用于描述包;
    • PARAMETER:用于描述参数;
    • TYPE:用于描述类、接口(包括注解类型)或者enum声明。
    b、创建一个java library apt-processor

    需要依赖auto-service注解

        implementation 'com.google.auto.service:auto-service:1.0-rc2'
        implementation project(path: ':apt-annotation')
    

    创建注解处理器,注意类上面需要添加注解@AutoService

    
    
    /**
     * Element中定义的一些常用的方法
     * asType 返回此元素的种类:包、类、接口、字段、方法
     * getModifiers 返回此元素的修饰符号
     * getSimpleName 返回此元素的简单名称  如类名
     * getEnclosedElements 返回封装此元素的最里面元素
     * getAnnotation 返回此元素针对指定类型的注解
     * */
    
    /***
     *Element 5个直接子类
     * TypeElement 一个类或者接口程序元素
     * VariableElement 代表一个字段、enum常量、方法、构造参数、局部变量或异常参数
     * ExecutableElement  某个类或者接口方法、构造方法或初始化程序(静态或实例),包括注解类型元素
     * PackageElement  一个包程序元素
     * ExecutableElement 某个类或者接口的方法、构造方法或者初始化程序(静态或者实例),包括注解类型元素
     * TypeParameterElement 一般类、接口、方法或构造方法元素的泛型参数
     *
     * */
    
    @AutoService(Process.class)
    public class SensorsDataBindViewProcessor  extends AbstractProcessor {
        private Elements elementUtils;
        private Map<String,SensorsDataClassCreateFactory> mClassCreatorFactoryMap = new HashMap<>();
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            //提供了很多工具类如 Elements \ Types \ Filer
    
            elementUtils = processingEnvironment.getElementUtils();
        }
    
        /**
         * 注解处理器是注册给哪个注解的
         * @return
         */
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
    
            HashSet<String> supportTypes = new LinkedHashSet<>();
            supportTypes.add(SensorsDataBindView.class.getCanonicalName());
            //CanonicalName : com.example.apt_annotation.SensorsDataBindView
            System.out.println("CanonicalName : " + SensorsDataBindView.class.getCanonicalName()) ;
    
            return supportTypes;
        }
    
        ///最小支持的java版本
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            System.out.println("process : " ) ;
            mClassCreatorFactoryMap.clear();
            //得到所有的注解
    
            Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(SensorsDataBindView.class);
            for (Element element : elementsAnnotatedWith) {
                //因为我们是方法注解,所以可以直接强转成VariableElement
                //VariableElement 代表一个字段、enum常量、方法、构造参数、局部变量或异常参数
                VariableElement variableElement = (VariableElement) element;
                //返回封装元素最里面的元素  就是类的全名
                TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
                //TypeElement : com.example.aptdemo.MainActivity
                System.out.println("TypeElement : "+classElement.toString());
                String fullClassName = classElement.getQualifiedName().toString();
                //fullClassName : com.example.aptdemo.MainActivity
                System.out.println("fullClassName : "+fullClassName);
                SensorsDataClassCreateFactory proxy = mClassCreatorFactoryMap.get(fullClassName);
                if(proxy == null){
                    proxy = new SensorsDataClassCreateFactory(elementUtils,classElement);
                    mClassCreatorFactoryMap.put(fullClassName,proxy);
                }
    
                SensorsDataBindView bindAnnotation = variableElement.getAnnotation(SensorsDataBindView.class);
                int id = bindAnnotation.value();
                proxy.putElement(id,variableElement);
    
            }
    
            //创建java文件
            for (String s : mClassCreatorFactoryMap.keySet()) {
                SensorsDataClassCreateFactory proxyInfo = mClassCreatorFactoryMap.get(s);
    //            try {
    //                //todo  xiecuolecreateSourceFile  写成了createClassFile
    //                JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
    //
    //                Writer writer = jfo.openWriter();
    //                writer.write(proxyInfo.generateJavaCode());
    //                writer.flush();
    //                writer.close();
    //                System.out.println("JavaFileObject : ");
    //            } catch (IOException e) {
    //                e.printStackTrace();
    //                System.out.println("IOException : ");
    //            }
    
                //javapoet
                JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCodeWithJavaPoet()).build();
                try {
                    javaFile.writeTo(processingEnv.getFiler());
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
    
            return true;
        }
    }
    
    • init 初始化函数,可以得到ProcessingEnvioment对象。ProcessingEnvironment提供很多有用的工具类,如Element、Types、Filer。
    • getSupportedAnnotedAnnotationTypes 指定这个注解处理器是注册给哪个注解的,这里指定的是我们上面创建的注解@SensorsDataBindView。
    • getSupportedSourceVersion 指定使用的java版本,通常都是返回SourceVersion.lastSupported。这个方法也可以使用@SupportedSourceVersion注解代替
    • process 这里可以扫描、评估、处理注解的代码,生产java文件。通过roundEnvironment.getElementsAnnotatedWith(SensorsDataBindView.class)可以得到所有含有@SensorsDataBindView注解的elements集合。可以将element强制转化为VariableElement类型,通过variableElement.getEnclosingElement()可以获取类的信息TypeElement,通过classElement.getQualifiedName().toString()可以获取完整的包名和类名。然后讲elements的信息以完整的包名和类名作为key保存到mClassCreateFactorFactoryMap中,最后通过mClassCreateFactoryMap创建对应的Java文件,其中mClassCreateFactoryMap是提供给SensorsDataClassCreateFactory的集合。

    SensorsDataClassCreateFactory 主要是拼接生成java文件的,源码如下

    
    public class SensorsDataClassCreateFactory {
        private String mBindingClassName;
        private String mPackageName;
        private TypeElement mTypeElement;
        private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
    
        public SensorsDataClassCreateFactory(Elements elements,TypeElement mTypeElement) {
            this.mTypeElement = mTypeElement;
            PackageElement packageElement = elements.getPackageOf(mTypeElement);
            String packgeName = packageElement.getQualifiedName().toString();
            String className = mTypeElement.getSimpleName().toString();
            this.mPackageName = packgeName;
            this.mBindingClassName = className + "_SensorsDataViewBinding";
    
        }
    
        public void putElement(int id,VariableElement element){
            mVariableElementMap.put(id,element);
        }
        /**
         * 创建java代码
         */
    
        public String generateJavaCode() {
            StringBuilder builder = new StringBuilder();
            builder.append("/**\n" +
                    " * Auto Created by SensorsData APT\n" +
                    " */\n");
            builder.append("package ").append(mPackageName).append(";\n");
            builder.append('\n');
            builder.append("public class ").append(mBindingClassName);
            builder.append(" {\n");
    
            generateBindViewMethods(builder);
            builder.append('\n');
            builder.append("}\n");
            return builder.toString();
        }
    
        /**
         * 加入Method
         *
         * @param builder StringBuilder
         */
        private void generateBindViewMethods(StringBuilder builder) {
            builder.append("\tpublic void bindView(");
            builder.append(mTypeElement.getQualifiedName());
            builder.append(" owner ) {\n");
            for (int id : mVariableElementMap.keySet()) {
                VariableElement element = mVariableElementMap.get(id);
                String viewName = element.getSimpleName().toString();
                String viewType = element.asType().toString();
                builder.append("\t\towner.");
                builder.append(viewName);
                builder.append(" = ");
                builder.append("(");
                builder.append(viewType);
                builder.append(")(((android.app.Activity)owner).findViewById( ");
                builder.append(id);
                builder.append("));\n");
            }
            builder.append("  }\n");
        }
    
        public TypeSpec generateJavaCodeWithJavaPoet(){
            TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(generateMethodsWithJavaPoet())
                    .build();
    
            return  bindingClass;
        }
    
        public MethodSpec generateMethodsWithJavaPoet(){
            ClassName owner = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bindView")
            .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(owner,"owner");
    
            for (Integer integer : mVariableElementMap.keySet()) {
                VariableElement variableElement = mVariableElementMap.get(integer);
                String viewName = variableElement.getSimpleName().toString();
                String viewType = variableElement.asType().toString();
                methodBuilder.addCode("owner."+viewName+ " = "+"("+viewType+")(((android.app.Activity)owner).findViewById("+integer
                        +"));");
            }
            return methodBuilder.build();
        }
    
        public String getPackageName() {
            return mPackageName;
        }
    
        public String getProxyClassFullName(){
            return mPackageName + "." + mBindingClassName;
        }
    
        public TypeElement getTypeElement(){
            return mTypeElement;
        }
    }
    
    c.创建一个Android module apt-sdk

    通过反射调用生成的SensorsDataViewBinding.java的bindView方法。
    需要依赖apt-annottion

    public class SensorsDataAPI {
        public static void bindView(Activity activity){
            Class<? extends Activity> aClass = activity.getClass();
            try {
                Class<?> bindViewClass = Class.forName(aClass.getName() + "_SensorsDataViewBinding");
                Method method = bindViewClass.getMethod("bindView", activity.getClass());
                method.invoke(bindViewClass.newInstance(),activity);
    
            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
    

    到这里基本完成。源码上传到github:https://github.com/yangzai100/APTDemo/tree/master

    相关文章

      网友评论

          本文标题:Butterknife原理

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