Android 手写 ButterKnife 源码

上次已经分析了 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 源码链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • 平常在写java程序的时候,有时会用到一些方法,但是在IDE中调用不到这些方法,进入到源码中,会发现这些方法的上面...
    月影路西法阅读 1,164评论 0 2
  • 一、什么是反射 反射机制:允许运行中的Java程序对自身进行检查,并能直接操作程序的内部属性或方法。允许程序在正在...
    viky_lyn阅读 2,496评论 1 3
  • 官方定义:反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。在计算机科学领域,反射是一类应用,它们能...
    KnowlesVan阅读 2,063评论 0 2
  • 01、反射 主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。 02、反射作用 反编译:将 class ...
    IT一书生阅读 2,211评论 0 12
  • 夜莺2517阅读 127,717评论 1 9