美文网首页待写
Android 手写 ButterKnife 源码

Android 手写 ButterKnife 源码

作者: 是刘航啊 | 来源:发表于2021-03-19 16:36 被阅读0次

    上次已经分析了 ButterKnife 源码分析

    准备工作

    • butterknife ( Android Library )
      绑定

    • butterknife - annotation ( Java Library )
      自定义注解

    • butterknife - compiler ( Java Library )
      注解处理器

    1、处理注解

    @Retention(RetentionPolicy.CLASS)//生命周期
    @Target(ElementType.FIELD)//类型
    public @interface BindView {
        int value();
    }
    

    2、注解处理器

    @AutoService(Processor.class)
    public class ButterKnifeProcessor extends AbstractProcessor {
        //文件
        Filer filer;
        //节点工具类
        Elements elements;
    
        static final String superClassPackage = "com.darren.butterknife";
        static final String unBinder = "UnBinder";
        static final String util = "Utils";
    
        //初始化工具
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
    
            filer = processingEnv.getFiler();
            elements = processingEnv.getElementUtils();
        }
    
        //设置支持的注解
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new HashSet<>();
            types.add(BindView.class.getCanonicalName());
            return types;
        }
    
        //支持的版本
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return processingEnv.getSourceVersion();
        }
    
        //核心方法
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            //获取当前源码中所有使用 @BindView 的变量节点
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
            //存储每个 Activity 对应的节点
            Map<TypeElement, List<VariableElement>> map = new HashMap<>();
            //便利 Set 集合
            for (Element element : elements) {
                //变量节点
                VariableElement variableElement = (VariableElement) element;
                //类节点
                TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
                //根据类节点获取属性节点
                List<VariableElement> variableElementList = map.get(typeElement);
                //判断当前的类节点对应的属性节点集合
                if (variableElementList == null) {
                    variableElementList = new ArrayList<>();
                    map.put(typeElement, variableElementList);
                }
                variableElementList.add(variableElement);
            }
    
    
            //便利 Activity 中的节点,通过 JavaPoet 生成 Java 文件
            for (Map.Entry<TypeElement, List<VariableElement>> entry : map.entrySet()) {
                //获取类节点
                TypeElement typeElement = entry.getKey();
                //获取变量节点列表
                List<VariableElement> variableElements = entry.getValue();
                //获取类名
                String activityName = getClassName(typeElement);
                //获取包名
                String packageName = getPackageName(typeElement);
                //父类
                ClassName superClass = ClassName.get(superClassPackage, unBinder);
                //当前类
                ClassName className = ClassName.bestGuess(activityName);
                //创建类并继承UnBinder
                TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName + "_ViewBinding")
                        .addModifiers(Modifier.PUBLIC)
                        .addSuperinterface(superClass)
                        .addField(className, "target", Modifier.PRIVATE);
    
                //创建 unbind 方法
                MethodSpec.Builder unbindBuilder = MethodSpec.methodBuilder("unbind")
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.PUBLIC);
                unbindBuilder.addStatement("$T target = this.target", className);
                unbindBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")");
                unbindBuilder.addStatement("this.target = null");
    
                //创建构造方法
                ClassName uiThreadClassName = ClassName.get("androidx.annotation", "UiThread");
                MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                        .addAnnotation(uiThreadClassName)
                        .addParameter(className, "target", Modifier.FINAL)
                        .addModifiers(Modifier.PUBLIC);
                constructorBuilder.addStatement("this.target = target");
    
                ClassName utilClass = ClassName.get(superClassPackage, util);
                //便利变量集合,在构造方法中完成 findViewById 逻辑
                for (VariableElement variableElement : variableElements) {
                    //通过注解拿到 id
                    int id = variableElement.getAnnotation(BindView.class).value();
                    //获取变量名
                    String fileName = variableElement.getSimpleName().toString();
                    //$L for Literals 替换字符串
                    //$T for Types 替换类型,可以理解成对象
                    constructorBuilder.addStatement("target.$L = $T.findViewById(target,$L,$T.class)", fileName, utilClass, id,variableElement.asType());
                    unbindBuilder.addStatement("target.$L = null", fileName);
                }
    
                //添加方法
                classBuilder.addMethod(unbindBuilder.build());
                classBuilder.addMethod(constructorBuilder.build());
    
                //将 Java 写成 Class 文件
                try {
                    JavaFile.builder(packageName, classBuilder.build())
                            .addFileComment("ButterKnifeProcessor")
                            .build()
                            .writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            return false;
        }
        
        //获取类名
        private String getClassName(TypeElement typeElement) {
            String className = typeElement.getSimpleName().toString();
            return className;
        }
        
        //获取包名
        private String getPackageName(TypeElement typeElement) {
            String packageName = elements.getPackageOf(typeElement).toString();
            return packageName;
        }
    }
    

    以上步骤完成就可以在 build 目录下找到对应的 ViewBinding

    MainActivity_ViewBinding

    3、绑定

    public class ButterKnife {
        public static UnBinder bind(Activity activity) {
            try {
                Class<?> clazz = Class.forName(activity.getClass().getName() + "_ViewBinding");
                Constructor<?> cons = clazz.getConstructor(activity.getClass());
                UnBinder unbinder = (UnBinder) cons.newInstance(activity);
                return unbinder;
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return UnBinder.EMPTY;
        }
    }
    

    4、总结

    第一步:处理自定义注解
    标明注解的生命周期和类型

    第二步:处理 Processor
    init 方法:初始化工具类
    getSupportedAnnotationTypes 方法:设置支持的注解
    process 方法:便利注解,使用 JavaPoet 生成 java 文件,并转换成 class 文件

    第三步:绑定
    绑定对应的 View

    主要的步骤就介绍完了,如果有什么不懂的地方可以看源码

    Github 源码链接

    相关文章

      网友评论

        本文标题:Android 手写 ButterKnife 源码

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