待写

Android 手写 ButterKnife 源码

2021-03-19  本文已影响0人  是刘航啊

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

准备工作

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 源码链接

上一篇下一篇

猜你喜欢

热点阅读