编写编译期注解

本文主要是记录一次Java编译时注解的学习,从ButterKnife的原理去了解编译时注解

  1. 自定义注解
@Target(ElementType.FIELD)  // 注解作用域
@Retention(RetentionPolicy.SOURCE)  // 注解生命周期
public @interface BindView {

    // 控件id
    long value();
}
  1. 注解其实就相当于是一种标识,要对我们打上标记的地方做处理,就需要我们自定义注解处理器来处理
/**
 3. 拿到我们自定义的注解,帮我们生成相关代码(findViewById)
 */
@AutoService(Processor.class)
public class WatermelonProcessor extends AbstractProcessor {

    // 生成Java文件的对象
    private Filer filer;

    /**
     * 注解处理器要处理的事情,生成Java文件
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        /*
         * public class ClassName {     TypeElement
         * private int i;               VariableElement
         * private void method()        ExecutableElement
         */
        // 获得所有使用了BindView注解的成员变量,也就是控件
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        // 创建一个装控件的节点容器,结构化所有控件
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elements) {
            VariableElement ve = (VariableElement) element;
            String activityName = getActivityName(ve);
            List<VariableElement> list = map.get(activityName);
            if (list == null) {
                list = new ArrayList<>();
                map.put(activityName, list);
            }
            list.add(ve);
        }
        // 开始生成文件
        // 遍历集合拿到类名
        for (String activityName : map.keySet()) {
            List<VariableElement> list = map.get(activityName);
            String newActivityName = activityName + "$$ViewBinder";
            String packageName = getPackageName(list.get(0));
            // 开始写文件
            Writer writer = null;
            try {
                JavaFileObject source = filer.createSourceFile(newActivityName);
                writer = source.openWriter();
                // 开始写第一行
                writer.write("package " + packageName + ";\n\n");
                // 第二行
                writer.write("public class " + newActivityName + " implements ViewBinder<" + activityName + "> {\n\n");
                // 第三行
                writer.write("\tpublic void bind(" + activityName + " target) {\n");
                // 为控件赋值
                for (VariableElement element : list) {
                    writer.write("\t\ttarget." + element.getSimpleName() + " = target.findViewById(" + element.getAnnotation(BindView.class).value() + ");\n");
                }
                // 结束
                writer.write("\t}\n}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return false;
    }

    /**
     * 通过控件节点拿到包名
     */
    private String getPackageName(VariableElement element) {
        return processingEnv.getElementUtils().getPackageOf(element).toString();
    }

    /**
     * 通过控件节点,获取这个节点的activity name
     */
    private String getActivityName(VariableElement element) {
        // 获取上一层的节点
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        return typeElement.getSimpleName().toString();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        // 初始化处理文件的对象
        filer = processingEnvironment.getFiler();
    }

    /**
     * 声明我们的注解处理器要处理哪些注解
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new LinkedHashSet<>();
        set.add(BindView.class.getCanonicalName());
        return set;
    }

    /**
     * 告诉JVM,我们注解处理器的版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }
}

这一步的工作是将我们使用了注解的地方,帮我们生成相关的类来初始化打上标记的变量,不需要我们重复写一样的模板代码。就是拼接文件,写入文件的操作

  1. 在使用ButterKnife时,都要有个绑定的操作,将当前的对象传给ButterKnife,就会帮我们完成里面成员变量的初始化。自动生成的代码是
public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {

	public void bind(MainActivity target) {
		// 在调用bind方法时传进来的对象
		target.tv = target.findViewById(2131165326);
		target.btn = target.findViewById(2131165218);
	}
}
  1. 所有生成的类都实现了ViewBinder这个接口,这样做是为了更方便的调用里面的方法,也更高效,看看ViewBinder的源码
/**
 6. 所有生成的java文件都实现了这个接口
 7. @param <T>
 */
public interface ViewBinder<T> {

    void bind(T t);
}
  1. 进到bind方法源码
public static void bind(Activity activity) {
		// 拿到生成的类名
        String cName = activity.getClass().getCanonicalName() + "$$ViewBinder";
        try {
        	// 通过反射创建ViewBinder实例,再调用bind方法
        	// bind方法在自动生成的文件中已经做了实现
        	// 到这一步就已经帮我们实现了所有带有BindView注解属性的初始化工作
        	// 这里为什么要用反射???
            // 因为我们无法预知ViewBinder的实现类
            // 要根据类名动态创建实例
            // 换种说法,我们要创建的类还不存在,需要编译后才生成
            Class<?> clazz = Class.forName(cName);
            ViewBinder binder = (ViewBinder) clazz.newInstance();
            binder.bind(activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

后记:ButterKnife的代码远不止这些,但主流程都是差不多的,像ButterKnife里面对实例化过的对象还有缓存的操作等等。但对编译期注解的学习,这是一个很好的入手点,写完马上就能看到效果。
最后附上githup地址:https://github.com/rongjianrun/WatermelonKnife.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值