什么是APT

APT全称 Annotation Process Tool,是Java提供的注解处理工具,能够帮助开发者在编译阶段生成所需要的可执行代码。

在Android开发中,著名的ButterKnifeDagger2ARouter都是使用了APT技术,所以作为一名Android开发者,还是有必要了解一下APT技术的使用。

Android中使用APT

在Android工程中使用APT,首先需要创建两个Java Module,一个是annotation(注解module,也可以是Android Module),一个是compiler(这个是编译处理器,必须是java module)。

这里以Demo:https://gitee.com/renzhongrui/android-learn/tree/master/031-android-butterknife为例,使用APT编写简单的butterknife原理。

一、创建lib_butterknife_annotation注解module,并添加注解BindView

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

@Target:用于描述注解的使用范围

常用注解ElementType如下:

ElementType

说明

FIELD

作用于成员变量上

METHOD

作用于方法上

TYPE

用于描述类Class、接口interface(包括注解annotation类型) 或enum

TYPE_USE

作用于任何类型上

@Retention

定义注解保留周期,一共有三种策略,定义在RetentionPolicy枚举中:

RetentionPolicy

说明

source

注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃,被编译器忽略。

class

注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。

runtime

注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

二、创建lib_butterknife_compiler注解处理器

1、创建java module注解处理器,并在build.gradle中添加依赖:

// 添加自定义注解依赖
implementation project(':lib_butterknife_annotation')
// 添加注解处理库
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
// 创建的java module默认会使用android.jar中的api,这里需要引入本地jdk中的jar包
implementation files ('D:/Program Files/Java/jdk1.8.0_74/jre/lib/rt.jar')

这里需要注意一点,如果代码中引入不了javax包下的jdk中的类,表示依赖的是android.jar下的jdk,所以需要手动引入一下本地的jdk的jar包,在项目工程的Exterenal Libraries下可以找到当前工程的jdk目录。

2、创建AnnotationCompiler类,需要继承AbstractProcessor

AutoService会自动在build/classes输入目录下生成文件META-INF/services/javax.annotation.processing.Processor,在javax.annotation.processing.Processor的情况下,如果一个jar中包含metadata文件,并且在javac的classpath下,javac会自动加载这个jar,同时包含它的普通注解编译环境。

3、继承AbstractProcessor类后需要重写四个方法

init

初始化方法,会被首先调用,在init方法中初始化文件操作对象Filer实例和打印对象Messager实例。

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    messager = processingEnv.getMessager(); // 打印对象
    filer = processingEnv.getFiler(); // 文件操作对象
}

getSupportedSourceVersion 声明支持的Java版。

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

getSupportedAnnotationTypes

获取注解类型,这个类型就是获取之前声明的注解类型。

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

process

process方法主要是执行生成java文件,然后在java文件中写入想在编译阶段执行的代码逻辑。

这里TypeElement类型有以下几种:

  • TypeElement :类或者接口类型
  • VariableElement:成员变量
  • ExecuteableElement:成员方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
     // 得到BindView注解的Element节点
    Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);
    // 存储每个Activity中的所有注解属性
    HashMap<String, List<VariableElement>> map = new HashMap<>();
    for (Element element : bindViewElements) {
    	// 转成属性节点
        VariableElement e = (VariableElement) element;
        // 获取上一个节点,也就是类名
        TypeElement typeElement = (TypeElement) e.getEnclosingElement();
        // 获取类名
        String activityName = typeElement.getSimpleName().toString();
        //缓存对应类的所有节点
        List<VariableElement> variableElementList = map.get(activityName);
        if (variableElementList == null) {
            variableElementList = new ArrayList<>();
            map.put(activityName, variableElementList);
        }
        // 缓存所有的属性注解节点
        variableElementList.add(e);
    }
    // 开始生成java文件,将逻辑自动写入到java文件中
    if (map.size() > 0) {
        Writer writer = null;
        Iterator<String> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            String activityName = iterator.next();
            // 得到类上的所有节点
            List<VariableElement> variableElements = map.get(activityName);
            //得到包名
            String packageName = getPackageName(variableElements.get(0));
            messager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
            // 自定义类名,仿造butterknife
            String newClass = activityName + "$$ViewBind";
            try {
                // 创建java文件
                JavaFileObject javaFile = filer.createSourceFile(packageName + "." + newClass);
                // 写入代码
                writer = javaFile.openWriter();
                StringBuffer buffer = new StringBuffer();
                // 拼接代码
                buffer.append("package " + packageName + ";\n");
                buffer.append("import android.view.View;\n");
                buffer.append("public class " + newClass + "{\n");
                buffer.append("public " + newClass + "(final " + packageName + "." + activityName + " target){\n");
                for (VariableElement element : variableElements) {
                    String fieldName = element.getSimpleName().toString();
                    int resId = element.getAnnotation(BindView.class).value();
                    TypeMirror typeMirror = element.asType();
                    // 执行findViewById
                    buffer.append("target." + fieldName + "=target.findViewById(" + resId + ");\n");
                }
                buffer.append("}\n}");
                writer.write(buffer.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
    return false;
}

测试

在app/build.gradle中添加依赖:

implementation project(':lib_butterknife_annotation')
annotationProcessor project(':lib_butterknife_compiler')

在MainActivity中添加代码:

public class MainActivity extends AppCompatActivity  {

    @BindView(R.id.btn_write)
    Button btn_write;

    @BindView(R.id.btn_read)
    Button btn_read;
    
}

build一下在路径031-android-butterknife\app\build\generated\source\apt\debug\com\learn\eventbus\MainActivity$$ViewBind.java下

最终生成的类如下:

package com.learn.eventbus;
import android.view.View;
public class MainActivity$$ViewBind{
public MainActivity$$ViewBind(final com.learn.eventbus.MainActivity target){
target.btn_write=target.findViewById(2131165250);
target.btn_read=target.findViewById(2131165249);
}
}

4、当然最后要调用一下

创建ButterKnife.java类,添加代码:

public class ButterKnife {
    public static void bind(Object activity) {
        // 获取当前类
        Class<?> aClass = activity.getClass();
        // 自定义生成的类名
        String bindName = aClass.getName() + "$$ViewBind";
        try {
            // 执行带参数的构造方法,这里调用的就是上面生成的类的构造,会执行findViewById方法
            Class<?> bindClazz = Class.forName(bindName);
            Constructor<?> constructor = bindClazz.getConstructor(activity.getClass());
            constructor.newInstance(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

以上就是使用APT实现ButterKnife核心原理,感兴趣的可以下载Demo测试一下。

最后如果本文对您有帮助,麻烦点个赞,如果想手机浏览。