Android 中使用APT

553 阅读7分钟

APT的全称是AnnotationProcessingTool,它是一种注解处理器,在编译期间通过注解生成.java文件。既然是注解处理工具,那首先得有注解,注解的一般形式如下:

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

首先看到定义注解类需要关键字@Interface,然后在注解类的上面有两个注解

  • @Retention:这个字段是定义该注解的生效时机,一共有三种类型SOURCE(只在源码阶段有效,也就是.java为转换成.class之前)、CLASS(编译期有效,就是说会被保留在.class文件中,但不会被虚拟机保留)、RUNTIME(会被保留到运行时,也就是说在运行时可以通过反射区获取注解信息)
  • @Target:这个字段是声明注解所修饰的对象范围,一共有这几类TYPE(作用在类、接口,enum声明上),FIELD(作用在字段上(变量)),METHOD(作用在方法上),PARAMETER(作用在参数上),CONSTRUCTOR(作用在构造函数上),LOCAL_VARIABLE(作用在局部变量上),ANNOTATION_TYPE(作用在注解上),PACKAGE(作用在包上),TYPE_PARAMETER(用于类型参数声明1.8引入的)

其次在注解中定义了需要传递的参数名子为value,类型为int,注解支持的元素数据类型除了上述的int,还支持如下数据类型

  • 所有基本类型(int,float,boolean,byte,double,char,long,short
  • String
  • Class
  • enum
  • Annotation
  • 上述类型的数组

介绍完了注解之后,我们再看看APT 。我们直接从他的使用开始。

  • 新建Android工程名字为APTTest
  • 新建module(JavaLibrary)lib_annotation,这里主要存放注解,这里就一个类就是上面的BIndVIew
  • 新建module(JavaLibrary)lib_processor,这里主要就是apt的内容。 在这里首先分两步,第一步新建一个类继承自AbstractProcessor,这里实现的processor其中的方法都已经在注释中
public class APTTestProcessor extends AbstractProcessor {
    /**
     * Elements提供了一些和元素相关的操作,如获取所在包的包名等
     */
    private Elements mElementsUtil;
    /**
     * Filer用于文件操作,我们用它去创建生成的代码文件
     */
    private Filer mFilerUtil;
    /**
     * 提供了和类型相关的操作,比如获取父类,两个类是不是父子关系
     */
    private Types mTypesUtil;
    /**
     * 用于打印的,它会打印出Element所在的源代码,它还会抛出异常。靠默认的错误打印有时很难找出错误的地方,我们可以用它去添加更直观的日志打印
     */
    private Messager mMessagerUtil;

    /**
     * 初始化,可以得到processingEnvironment,这个对象中包含了一些工具类如Elements,Types和Filer,Messager
     *
     * @param processingEnvironment
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        this.mElementsUtil = processingEnvironment.getElementUtils();
        this.mFilerUtil = processingEnvironment.getFiler();
        this.mMessagerUtil = processingEnvironment.getMessager();
        this.mTypesUtil = processingEnvironment.getTypeUtils();
    }

    /**
     * 具体的处理过程
     *
     * @param set:在getSupportedAnnotationTypes中声明的那些注解
     * @param roundEnvironment:上下文环境
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        System.out.println("APTTestProcessor is working!");
        //   mMessagerUtil.printMessage(Diagnostic.Kind.ERROR,"ssssss");
        return true;
    }

    /**
     * 指定该处理器要处理的注解
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(BindView.class.getCanonicalName());
    }

    /**
     * 指定使用的Java版本,一般都是latestSupported
     * 如果要指定某一具体的话直接return如 SourceVersion.RELEASE_8
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
//        return SourceVersion.RELEASE_8;
        return SourceVersion.latestSupported();
    }
}

第二步新建文件路径为main/resources/META-INF/services/javax.annotation.processing.Processor,文件内容就一行代码,指定processor的全路径(注意上面加粗的内容一个字都不能错),com.mwy.test.lib_processor.APTTestProcessor

  • 在APTTest中添加对这两个library的依赖,注意,注解处理器的依赖有点特殊,采用的是annotationProcessor
  • 在lib_processor中添加lib_annotation的引用

这时候就可以使用了,我们build一下app可以看到如下信息

打印成功,所以我们这个apt工具是可以使用的。从上面的过程中我们可以看到真正起作用的事这个processor,而这个processor的相关方法的说明已经在注释中了,不再赘述。它最重要的方法就是process()它的参数一个是声明的所有注解,另一个则是每一轮的上下文环境,从这里我们可以拿到当前处理的元素(Element)(也就是类、方法、字段等等),而这些信息是通过Element封装的。那我们就具体的看一下Element

public interface Element extends AnnotatedConstruct {
    /**
     * @return:返回元素定义的类型
     */
    TypeMirror asType();

    /**
     * @return:返回此元素的种类:包、类、接口、方法、字段等
     */
    ElementKind getKind();

    /**
     * @return:返回此元素修饰符:public,private,static等
     */
    Set<Modifier> getModifiers();

    /**
     * @return:返回元素的简单名称,如类名
     */
    Name getSimpleName();

    /**
     * @return:返回包含当前元素的外层元素,后面会具体介绍
     */
    Element getEnclosingElement();

    /**
     * @return:返回封装此元素的最里层元素,后面会具体介绍
     */
    List<? extends Element> getEnclosedElements();

    boolean equals(Object var1);

    int hashCode();

    /**
     * @return:此元素中所有的注解
     */
    List<? extends AnnotationMirror> getAnnotationMirrors();

    /**
     * @param var1
     * @param <A>
     * @return:返回此元素针对指定类型的注解
     */
    <A extends Annotation> A getAnnotation(Class<A> var1);

    <R, P> R accept(ElementVisitor<R, P> var1, P var2);
}

既然是一个接口,那势必会有他的具体实现类,这里直接的有5个:

  • TypeElement:一个类或者接口程序元素,它的getEnclosingElement()返回的是当前类的包,getEnclosedElements()返回的是类或接口中直接声明的字段,方法,构造函数和成员类型。
  • VariableElement:一个字段、enum常量,方法或构造方法参数、局部变量或者异常参数,它没有getEnclosedElement(),只有getEnclosingElement(),它返回的是TypeElement
  • ExecutableElement:某个类或接口中的方法,构造方法或者初始化程序,他没有getEnclosingElement及getEnclosedElement方法
  • PackageElement:一个包程序元素,它的getEnclosingElement返回的是null,getEnclosedElement返回的是当前包中顶级的class和interface
  • TypeParameterElement:一个类,接口、方法或构造方法元素的泛型参数,它的getEnclosingElement()返回的是由此中类型参数参数化的泛型接口、类或者构造方法。

接下来我们就可以正式的开始了。直接上代码

public class APTTestProcessor extends AbstractProcessor {
    /**
     * Elements提供了一些和元素相关的操作,如获取所在包的包名等
     */
    private Elements mElementsUtil;
    /**
     * Filer用于文件操作,我们用它去创建生成的代码文件
     */
    private Filer mFilerUtil;
    /**
     * 提供了和类型相关的操作,比如获取父类,两个类是不是父子关系
     */
    private Types mTypesUtil;
    /**
     * 用于打印的,它会打印出Element所在的源代码,它还会抛出异常。靠默认的错误打印有时很难找出错误的地方,我们可以用它去添加更直观的日志打印
     */
    private Messager mMessagerUtil;

    /**
     * 初始化,可以得到processingEnvironment,这个对象中包含了一些工具类如Elements,Types和Filer,Messager
     *
     * @param processingEnvironment
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        this.mElementsUtil = processingEnvironment.getElementUtils();
        this.mFilerUtil = processingEnvironment.getFiler();
        this.mMessagerUtil = processingEnvironment.getMessager();
        this.mTypesUtil = processingEnvironment.getTypeUtils();
    }

    /**
     * 具体的处理过程
     *
     * @param set:在getSupportedAnnotationTypes中声明的那些注解
     * @param roundEnvironment:上下文环境
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        //因为注解是Variable上的,所以它的root就是TypeElement,所以packageName就为getEnclosingElement
        //而当前rootElement就是class
        for (Element rootElement : roundEnvironment.getRootElements()) {
            
            String packageStr = rootElement.getEnclosingElement().toString();
            String classStr = rootElement.getSimpleName().toString();

            //新生成的className
            ClassName className = ClassName.get(packageStr, classStr + "Binding");

            //在类中生成新的方法public void bindView(){}
            MethodSpec.Builder builder = MethodSpec.methodBuilder("bindView")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(ClassName.get(packageStr, classStr), "activity");

            boolean hasAnnotation = false;
            //既然是类,TypeElement,那么就遍历这个类,找到注解为BindView的Variable
            for (Element enclosedElement : rootElement.getEnclosedElements()) {
                if (enclosedElement.getKind() == ElementKind.FIELD) {
                    BindView bindView = enclosedElement.getAnnotation(BindView.class);
                    if (bindView != null) {
                        hasAnnotation = true;
                        builder.addStatement("activity.$N = activity.findViewById($L)", enclosedElement.getSimpleName(), bindView.value());
                    }
                }

            }
            //创建一个class
            TypeSpec builtClass = TypeSpec.classBuilder(className)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(builder.build())
                    .build();
            
            if (hasAnnotation) {
                try {
                    JavaFile.builder(packageStr, builtClass)
                            .build().writeTo(mFilerUtil);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

        return true;
    }

    /**
     * 指定该处理器要处理的注解
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(BindView.class.getCanonicalName());
    }

    /**
     * 指定使用的Java版本,一般都是latestSupported
     * 如果要指定某一具体的话直接return如 SourceVersion.RELEASE_8
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

我觉得重要的都在注释中写了,在编译之后就可以看到生成了一个新的类

所以已经实现了这个的自动绑定,接下来就是如何让程序主动运行这段代码了,当然是用反射了。我们在定义一个module(AndroidLib)lib_dealwith:这里面就新建一个文件,通过反射调用刚才新建类中的bindView方法

public class BindingApi {
    public static void bindView(Activity activity) {
        try {
            Class clazz = Class.forName(activity.getClass().getCanonicalName() + "Binding");
            Method method = clazz.getMethod("bindView", activity.getClass());
            method.invoke(clazz.newInstance(), activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

这时候就可以完整的使用了,比如下面

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv)
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BindingApi.bindView(this);

        textView.setText("this is autoBinding result");
    }
}

只需在Activity中添加注解,以及在setContenVIew后调用bindVIew方法,就完成了view的初始化。