java转cpp java转cpp工具_ide

1. APT简介

1.1 什么是APT?

APT(Annotation Processing Tool)即注解处理器,它是一种处理注解的工具,也是javac中的一个工具。APT可以用来在编译时扫描和处理注解。

1.2 APT的作用

通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。在Android中有如ButterKnife、Dagger、EventBus等第三方框架,都采用了APT。
注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。

2. 创建APT的项目结构

java转cpp java转cpp工具_工具类_02

2.1 创建Android Module命名为app(主工程)
dependencies {
    ...
    // 导入自定义注解
    implementation project(":apt-annotation")
    // 指定注释处理器
    annotationProcessor project(":apt-compiler")
    ...
}
2.2 创建Java library Module命名为 apt-annotation 存放自定义注解

gradle配置文件如下:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"
2.3 创建Java library Module命名为 apt-compiler 依赖 apt-annotationauto-service

gradle配置文件如下:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation project(":apt-annotation")

    // 注册注解,并对其生成META-INF的配置信息
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    // 第三方自动生成代码框架(可以通过类的调用方式来自动生成代码,简洁高效)
    implementation 'com.squareup:javapoet:1.10.0'
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

为什么两个模块一定要是Java Library而不是Android Library?

  • 如果创建Android Library模块会发现不能找到AbstractProcessor这个类,这是因为Android平台是基于OpenJDK的,而OpenJDK中不包含APT的相关代码。因此,在使用APT时,必须在Java Library中进行。

3. AbstractProcessor分析(核心)

AbstractProcessor是一个抽象类,需要通过继承它来实现我们自己的注解解释器

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    /**
     * 注解处理器的初始化阶段,可以通过ProcessingEnvironment来获取一些帮助我们来处理注解的工具类
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    /**
     * 指明有哪些注解需要被扫描到,返回注解的全路径(包名+类名)
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    /**
     * 用来指定当前正在使用的Java版本,一般返回SourceVersion.latestSupported()表示最新的java版本即可
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    /**
     * 核心方法,注解的处理和生成代码都是在这个方法中完成
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        return false;
    }
}
  • @AutoService的作用是用来生成META-INF/services/javax.annotation.processing.Processor文件,并且自动将该注解标记的注解处理器添加到该文件中。
  • 在注解处理器的初始化阶段,可以通过参数ProcessingEnvironment来获取一些帮助我们来处理注解的工具类
// Element操作类
Elements elementUtils = processingEnv.getElementUtils();

// 类信息工具类
Types typeUtils = processingEnv.getTypeUtils();

// 日志工具类
Messager messager = processingEnv.getMessager();

// 文件工具类
Filer filer = environment.getFiler();
3.1 了解Element

在Java中Element是一个接口,表示一个程序元素,它可以指代包、类、方法或者一个变量。Element已知的子接口有如下几种:

  • PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问。
  • ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
  • TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
  • VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

我们就拿一个简单的类举例,就可以很容易理解这些元素。

不同类型Element其实就是映射了Java中不同的类元素

package com.aptdemo.user; // PackageElement

public class User { // TypeElement
    private long id; // VariableElement
    private String username; // VariableElement

    public long getId() { // ExecutableElement
        return id;
    }

    public void setId( // ExecutableElement
                    long id) { // VariableElement
        this.id = id;
    }

    public String getUsername() { // ExecutableElement
        return username;
    }

    public void setUsername( // ExecutableElement
                    String username) { // VariableElement
        this.username = username;
    }
}

4. APT实例讲解

仿造ButterKnife中的 @BindView 注解,自动生成findViewById代码

4.1 在apt-annotation模块中自定义注解 @BindView
@Target(ElementType.FIELD) // 作用于成员属性上
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
4.2 在apt-compiler模块中,编写相关的注解处理逻辑

解析相关的节点信息存放到NodeInfo

public class NodeInfo {
    /**
     * 包路径
     */
    private String packageName;
    /**
     * 节点所在类名称
     */
    private String className;
    /**
     * 节点类型名称
     */
    private String typeName;
    /**
     * 节点名称
     */
    private String nodeName;
    /**
     * 注解的value
     */
    private int value;

    public NodeInfo(String packageName, String className, String typeName,
                    String nodeName, int value) {
        this.packageName = packageName;
        this.className = className;
        this.typeName = typeName;
        this.nodeName = nodeName;
        this.value = value;
    }

    public String getPackageName() {
        return packageName;
    }

    public String getClassName() {
        return className;
    }

    public String getTypeName() {
        return typeName;
    }

    public String getNodeName() {
        return nodeName;
    }

    public int getValue() {
        return value;
    }
}

编写核心的注解处理器

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
    /**
     * Element操作类
     */
    private Elements mElementUtils;
    /**
     * 类信息工具类
     */
    private Types mTypeUtils;
    /**
     * 日志工具类
     */
    private Messager mMessager;
    /**
     * 文件创建工具类
     */
    private Filer mFiler;

    /**
     * 节点信息缓存
     */
    private Map<String, List<NodeInfo>> mCache = new HashMap<>();

    /**
     * 注解处理器的初始化阶段,可以通过ProcessingEnvironment来获取一些帮助我们来处理注解的工具类
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mElementUtils = processingEnv.getElementUtils();
        mTypeUtils = processingEnv.getTypeUtils();
        mMessager = processingEnv.getMessager();
        mFiler = processingEnv.getFiler();
    }

    /**
     * 指明有哪些注解需要被扫描到,返回注解的全路径(包名+类名)
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(BindView.class.getCanonicalName());
    }

    /**
     * 用来指定当前正在使用的Java版本,一般返回SourceVersion.latestSupported()表示最新的java版本即可
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 核心方法,注解的处理和生成代码都是在这个方法中完成
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (annotations == null || annotations.isEmpty()) return false;

        // 获取所有@BindView节点
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
        if (elements == null || elements.isEmpty()) return false;

        // 遍历节点
        for (Element element : elements) {
            // 获取节点包信息
            String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
            // 获取节点类信息(如:MainActivity)
            String className = element.getEnclosingElement().getSimpleName().toString();
            // 获取节点类型(:如android.widget.TextView)
            String typeName = element.asType().toString();
            // 获取节点标记的属性名称(如:textView)
            String nodeName = element.getSimpleName().toString();
            // 获取注解的值(如:2131165354,是定义的id值)
            int value = element.getAnnotation(BindView.class).value();

            // 打印
            mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
            mMessager.printMessage(Diagnostic.Kind.NOTE, "className:" + className);
            mMessager.printMessage(Diagnostic.Kind.NOTE, "typeName:" + typeName);
            mMessager.printMessage(Diagnostic.Kind.NOTE, "nodeName:" + nodeName);
            mMessager.printMessage(Diagnostic.Kind.NOTE, "value:" + value);

            // 缓存KEY,使用全类名(包名+类名)
            String key = packageName + "." + className;
            // 缓存节点信息
            List<NodeInfo> nodeInfos = mCache.get(key);
            if (nodeInfos == null) {
                nodeInfos = new ArrayList<>();
                nodeInfos.add(new NodeInfo(packageName, className, typeName, nodeName, value));
                // 缓存
                mCache.put(key, nodeInfos);
            } else {
                nodeInfos.add(new NodeInfo(packageName, className, typeName, nodeName, value));
            }
        }

        // 判断临时缓存是否不为空
        if (!mCache.isEmpty()) {
            for (Map.Entry<String, List<NodeInfo>> stringListEntry : mCache.entrySet()) {
                try {
                    createFile(stringListEntry.getValue());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        return false;
    }

    /**
     * 创建文件,自动生成代码
     */
    private void createFile(List<NodeInfo> infos) throws IOException {
        NodeInfo info = infos.get(0);

        // 生成的文件名(类名)
        String className = info.getClassName() + "$$ViewBinding";

        // 方法参数模板:如 xxx(MainActivity target)
        ParameterSpec parameterSpec = ParameterSpec.builder(
                ClassName.get(info.getPackageName(), info.getClassName()), "target")
                .build();


        // 方法模板:如 public static void bind() {}
        MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addParameter(parameterSpec)
                .returns(void.class);

        // 给方法添加代码块
        for (NodeInfo nodeInfo : infos) {
            // target.textView = (TextView) target.findViewByID(R.id.text_view);
            methodSpecBuilder.addStatement("target.$L = ($L)target.findViewById($L)",
                    nodeInfo.getNodeName(),
                    nodeInfo.getTypeName(),
                    nodeInfo.getValue());
        }

        // 类模板,如:public class MainActivity$$ViewBinding {}
        TypeSpec typeSpec = TypeSpec.classBuilder(className)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(methodSpecBuilder.build())
                .build();

        // 生成文件
        JavaFile.builder(info.getPackageName(), typeSpec)
                .build()
                .writeTo(mFiler);
    }
}

javapoet的具体使用教程请参考:javapoet官方文档

4.3 在app模块中应用注解

在MainActivity中使用注解 @BindView

@BindView(R.id.text_view)
TextView textView;

然后再Rebuild Project,这样就可以在build->generated->ap_generated_sources->debug->包名路径下,看见自动生成的文件MainActivity$$ViewBinding

APT自动生成的文件MainActivity$$ViewBinding代码如下:

public class MainActivity$$ViewBinding {
  public static void bind(MainActivity target) {
    target.textView = (android.widget.TextView)target.findViewById(2131165354);
  }
}

这样我们就可以仿造ButterKnife,在App模块下新建一个ButterKnife类,利用反射来调用上面方法

public class ButterKnife {

    public static void bind(Activity target) {
        try {
            Class<?> clazz = target.getClass();
            // 反射获取apt生成的指定类
            Class<?> bindViewClass = Class.forName(clazz.getName() + "$$ViewBinding");
            // 获取它的方法
            Method method = bindViewClass.getMethod("bind", clazz);
            // 执行方法
            method.invoke(bindViewClass.newInstance(), target);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里我们的BindView注解功能就大功告成了

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.text_view)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
        textView.setText("Hello BindView");

    }
}

5. 参考

注解 - APT编译时注解处理器

javapoet官方文档