JavaPoet-编译时注入代码

JavaPoet 是 Square 公司推出的开源 Java代码生成框架,提供接口生成 Java 源文件。

它的项目主页及源码:https://github.com/square/javapoet

这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

例如业内的一些开源库 Butterknife,GreenDao,Dagger 2 等等,也都有运用 JavaPoet 结合注解来实现编译时动态生成代码。

以下是 JavaPoet 里面的一些关键类:
JavaFile : 用于构造输出包含一个顶级类的Java文件
TypeSpec :   生成类,接口,或者枚举
MethodSpec :    生成构造函数或方法
FieldSpec  :  生成成员变量或字段
ParameterSpec :   用来创建参数
AnnotationSpec  :  用来创建注解

 

下面参考 Butterknife 源码,实现绑定 View 和设置监听的功能:

1,首先是 注解类:

@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
  int value();
}

2,继承 AbstractProcessor ,实现 Processor :

@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 代码 输出到 哪
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        // 拿到 每个类,要生成的 代码块;
        Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndParseTargets(env);
        for (TypeElement typeElement : builderMap.keySet()) {
            List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
            // 去生成对应的 类文件;
            ViewBindHelper.writeBindView(typeElement, codeList, filer);
        }
        return true;
    }

    private Map<TypeElement, List<CodeBlock.Builder>> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();

        // 遍历带 对应注解的 元素,具体来看实际也就是 某个View对象
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
            ViewBindHelper.parseBindView(element, builderMap);
        }

        // 遍历带 对应注解的 元素,具体来看实际也就是 某个方法
        for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
            ViewBindHelper.parseListenerView(element, builderMap);
        }
        return builderMap;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

}

下面来看几个关键性代码,ViewBindHelper 类的 parseBindView 方法:

public static void parseBindView(Element element, Map<TypeElement, List<CodeBlock.Builder>> codeBuilderMap) {
        // 获取最里面的节点, 具体实际可能就是 某个Activity对象
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
        // 这个view是哪个类 Class
        String typeMirror = element.asType().toString();
        // 注解的值,具体实际可能就是 R.id.xxx
        int annotationValue = element.getAnnotation(BindView.class).value();
        // 这个view对象 名称
        String name = element.getSimpleName().toString();

        //创建代码块
        CodeBlock.Builder builder = CodeBlock.builder()
                .add("target.$L = ", name); //$L是占位符,会把后面的 name 参数拼接到 $L 所在的地方
        builder.add("($L)source.findViewById($L)", typeMirror, annotationValue);

        List<CodeBlock.Builder> codeList = codeBuilderMap.get(enclosingElement);
        if (codeList == null) {
            codeList = new ArrayList<>();
            codeBuilderMap.put(enclosingElement, codeList);
        }
        codeList.add(builder);
    }

看到这里你就知道根据 BindView 注解怎么生成 findViewById 的代码了。OnClick注解也是类似。

最后看怎么生成Java文件,ViewBindHelper 类的 writeBindView 方法:

    public static void writeBindView(TypeElement enclosingElement, List<CodeBlock.Builder> codeList, Filer filer) {
        // enclosingElement ,暗指 某个Activity.
        // 先拿到 Activity 所在包名
        String packageName = enclosingElement.getQualifiedName().toString();
        packageName = packageName.substring(0, packageName.lastIndexOf("."));
        // 再拿到 Activity 类名
        String className = enclosingElement.getSimpleName().toString();

        // 再拿到 Activity 是 哪个类 
        TypeName type = TypeName.get(enclosingElement.asType());//此元素定义的类型
        if (type instanceof ParameterizedTypeName) {
            type = ((ParameterizedTypeName) type).rawType;
        }

        ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

        // 创建构造方法 如果 Activity是 MainActivity,则会有 生成如下构造方法
//        public MainActivity_ViewBinding(final MainActivity target, final View source) {
//            target.btn1 = (android.widget.Button)source.findViewById(2131165217);
//            source.findViewById(2131165217).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
//        }
        MethodSpec.Builder methodSpecBuilder = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(type, "target", Modifier.FINAL)
                .addParameter(ClassName.get("android.view", "View"), "source", Modifier.FINAL);
        for (CodeBlock.Builder codeBuilder : codeList) {
            //方法里面 ,代码是什么
            methodSpecBuilder.addStatement(codeBuilder.build());
        }
        methodSpecBuilder.build();

        // 创建类 MainActivity_ViewBinding
        TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
                .addModifiers(Modifier.PUBLIC)
                .addMethod(methodSpecBuilder.build())
                .build();

        try {
            // 生成文件
            JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
            // 将文件写出
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

而主工程代码,看 MainActivity 里面怎么使用的:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn1)
    public Button btn1;

    @BindView(R.id.btn2)
    public Button btn2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyAnnotationUtils.bind(this);
        HelloWorld.main(null);

        btn1.setText("测试成功1");

        btn2.setText("测试成功2");


    }

    @OnClick({R.id.btn1, R.id.btn2})
    public void onBtn1Click(View v) {
        Toast.makeText(this, "测试" + ((Button)v).getText().toString(), Toast.LENGTH_SHORT).show();
    }
}

通过上面,整个项目编译完成后,在 app\build\generated\source\apt\debug\com\mill\apt 目录下面有一个自动生成的类:

package com.mill.apt;

import android.view.View;

public class MainActivity_ViewBinding {
  public MainActivity_ViewBinding(final MainActivity target, final View source) {
    target.btn1 = (android.widget.Button)source.findViewById(2131165217);
    target.btn2 = (android.widget.Button)source.findViewById(2131165218);
    source.findViewById(2131165217).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
    source.findViewById(2131165218).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
  }
}

而我们关心的这个类里面的方法是怎么调用呢?那就要看 MyAnnotationUtils类的 bind 方法:

public class MyAnnotationUtils {

    public static void bind(Activity activity) {
        //获取activity的decorView(根view)
        View view = activity.getWindow().getDecorView();
        bind(activity, view);
    }

    private static void bind(Object obj, View view) {
        String qualifiedName = obj.getClass().getName();
        //找到该activity对应的Bind类的名字
        String generateClass = qualifiedName + "_ViewBinding";
        //然后调用Bind类的构造方法,从而完成activity里view的初始化
        try {
            Class.forName(generateClass).getConstructor(obj.getClass(), View.class).newInstance(obj, view);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

实际就是通过反射,调用动态生成类的构造方法。

 

这里提几个需要注意的点:

1,HelloProcessor  类是必须在 Java Module里面的,不然找不到 javax.annotation.processing.AbstractProcessor;

2,自Android Gradle 插件 2.2 版本开始,官方提供了名为 AnnotationProcessor 的功能来完全代替 Android-Apt;

3,建议使用 com.google.auto.service:auto-service 库,注册注解处理器;

导入库之后,类似 加上 @AutoService(Processor.class) ,就会自动生成对应的

\META-INF\services javax.annotation.processing.Processor:

@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
    //......
}

 

最后附上demo地址:https://github.com/miLLlulei/AnnotationProcessor

欢迎大家star~

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值