Butterknife原理

2021-07-04  本文已影响0人  旺仔_100

一、目标

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

二、核心原理以及实现

核心原理

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

实现

项目主要分为四个模块

三、代码实现及对应讲解

a、创建一个java library apt-annotation

自定义注解@SensorsDataBindView

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

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

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

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

上一篇下一篇

猜你喜欢

热点阅读