写点什么

如何在 -Android- 中完成一个 -APT- 项目的开发?,android 文件下载实战

用户头像
Android架构
关注
发布于: 刚刚

如果不使用 auto-service 库,那么手动注册的方法如下:


1.在 Library 中创建 resources 文件夹;2.在 resources 中创建 META-INF 和 services 两个文件夹;3.在 services 中创建一个文件,命名为 javax.annotation.processing.Processor;4.在 javax.annotation.processing.Processor 文件中输入自己所创建的注解处理器类名(完整的,包括包名)。


3.创建自己的处理类,继承 AbstractProcessor,并使用 auto-service 注册。


举例:


@AutoService(Processor.class)public class AutoBundleProcessor extends AbstractProcessor


在创建 AbstractProcessor 子类后,我们需要重写其中的几个方法,来实现自己的处理逻辑:


@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment)


Processor 的初始化方法,在编译阶段会首先回调此方法,ProcessingEnvironment 类包含了解析需要的数据对象,我们可以通过它获取到一系列我们需要的其他对象,进而获取到需要的数据。


@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)


process 方法在编译过程中回调,在此我们可以获取到我们需要的类、对象及其对应的注解,在此可以分析并处理数据,最终生成我们需要的代码。


@Overridepublic Set<String> getSupportedAnnotationTypes()


getSupportedAnnotationTypes 方法帮助我们获得所需要的注解类。我们将自己需要的类名放入 Set 中并返回给注解处理器,换句话说,在这里为注解处理器指定需要处理哪些注解。


4.在项目中引用


在主项目的 gradle 中引用包含注解的 Android Library 引用注解器所在的 Java Library。由于 kotlin 的引入,建议使用 kapt 而非 annotationProcessor。


举例:


kapt?project(':libProce')


至此,工程整体结构已经搭建完成。


后续将介绍 APT 中各种类和对象的作用,以及如何实现我们需要的功能。



###APT 中的数据类型与概念


####1.ProcessingEnvironment


当我们在子类中复写了 AbstractProcessor 的 init 方法时,其参数就是一个 ProcessingEnvironment 对象。它内部提供了实用的对象,如 Elements、Types、Filer,在 APT 过程中都具有重要作用。我们可以获取到这些对象,来实现我们需要的功能。


####2.Element


在 APT 阶段,任何事物都被称为元素。比如一个对象、一个类、一个方法、一个参数。在 APT 中,它们都被统一称为元素。Element 本身是一个接口,也有多个子类,比如 TypeElement、VariableElement,子类在其基础上增加了额外的接口方法来描述具体事物的特殊属性。


####3.ElementKind


由于在 APT 中,任何事物都被称为元素,所以我们需要知道某个元素究竟是什么,这时候可以通过 ElementKind 判断。


ElementKind 是一个枚举类。其中包括但不限于 PACKAGE(包)、CLASS(类)、INTERFACE(接口)、FIELD(变量)、PARAMETER(参数)、METHOD(方法)等。这些都是我们开发中的基本概念。


####4.Elements


Elements 可以理解为一个工具类,它的功能就是操作 Element 对象,对 Element 对象进行一些处理或取值。


####5.TypeElement


TypeElement 是 Element 子类,它表示这个元素是一个类或者接口


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


。当 Element 满足条件时候,可以强转为一个 TypeElement 对象。


####6.VariableElement


VariableElement 是 Element 子类,它表示这个元素是一个变量、常量、方法、构造器、参数等。当 Element 满足条件时候,可以强转为一个 VariableElement 对象。


####7.Filer


Filer 是一个文件操作的接口,它可以创建或写入一个 Java 文件。主要针对的是 Java 文件对象,和一般文件的区别在于这是专门处理 Java 类文件的,以.java 或.class 为后缀的文件。在 APT 过程中,如果我们自动化代码生成完毕,需要生成一个.java 或.class 文件的时候,就需要用到 Filer。


####8.Name


Name 类是 CharSequence 的子类,主要表示类名、方法名。大部分情况下可以认为它和 String 等价。


####9.Types


Types 可以理解为一个工具类,是类型操作工具,在 APT 阶段,我们需要知道一个变量是 int 还是 boolean,那将需要通过 Types 相关类处理。它可以操作 TypeMirror 对象。


####10.TypeMirror


TypeMirror 表示数据类型。比如基本类型 int、boolean,也可以表示复杂数据类型,比如自定义类、数组、Parcelable 等。


####11.Modifier


即修饰词。比如声明一个变量时候,private static final 这些均为修饰词。大部分被 Android Studio 标示为蓝色的都是修饰词(除了 class int interface 这些)。


注:如果一个类中的变量缺省作用范围,那么修饰词为 default。


####12.RoundEnvironment


当我们在子类中复写了 AbstractProcessor 的 process 方法时,其参数就是一个 RoundEnvironment 对象。可以通过 RoundEnvironment 对象获取到我们在代码中设置了相关注解的 Element。


###APT 处理过程拆解


下面将以上文中所举出的场景,逐步对 APT 处理过程进行拆解,最终获取到我们需要的属性,为生成自动化代码做准备。


在 TestActivity 中的变量上设置注解:


@AutoBundlepublic int id;@AutoBundlepublic String name;@AutoBundlepublic boolean is;


其中 AutoBundle 注解是我们自己定义的注解类。


初步设计好后,我们需要在 process 方法中重写我们的逻辑:


@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)


第一步:


获取所有被 AutoBundle 注解所声明的元素。这里我们知道,其实只有三个变量;


for (Element element : roundEnvironment.getElementsAnnotatedWith(AutoBundle.class)) {


}


第二步:


对每个循环中的 Element 对象,获取其数据信息;


if (element.getKind() == ElementKind.FIELD) {// 可以安全地进行强转,将 Element 对象转换为一个 VariableElement 对象 VariableElement variableElement = (VariableElement) element;// 获取变量所在类的信息 TypeElement 对象 TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();


variableElement 中包含的数据包括修饰词、类型、变量名等;


typeElement 中包含的数据包括类名、包名等。


// 获取类名 String className = typeElement.getSimpleName().toString();


// 获取包名 String packageName = elements.getPackageOf(typeElement).getQualifiedName().toString();


// 获取变量上的注解信息 AutoBundle autoBundle = variableElement.getAnnotation(AutoBundle.class);boolean require = autoBundle.require();


// 获取变量名 Name name = variableElement.getSimpleName();


// 获取变量类型 TypeMirror type = variableElement.asType();


对于我们上文定义的某个变量,比如:


@AutoBundle(require = true)public int id;


那么获取到数据后:


require = truename = “id”type = int.class


其他两个变量同理。


三次循环将获取到我们需要的所有信息。


包括三个变量的注解值、变量名、类型。同时我们也获取到了 TestActivity 的类名和包名。可以对这些数据进行一些封装和缓存。接下来就可以自动化生成代码了。


我将上述变量值封装为 ClassHoder 与 FieldHolder 类中,ClassHolder 保存了类名、包名等信息,FieldHolder 保存了每个变量类型、变量名、注解等信息。下面将用这些保存好的数据,通过 JavaPoet 生成代码。


###JavaPoet 代码自动化生成


JavaPoet 是 Java 代码自动生成框架,是一个 github 上的开源项目,地址:https://github.com/square/javapoet


JavaPoet 简化了 Java 代码生成的开发难度,通过建造者模式,使调用更加人性化,可读性提升。具有自动 import 的功能,不需要再手动指定。


JavaPoet 中,大部分数据类型使用了 APT 中通用的类型,结合 APT 自动化产生代码非常方便快速。


1.TypeSpec.Builder


TypeSpec.Builder 是类的构建类,这里的类是广义上的,可以是一个 class、interface、annotation 等。



示例代码:


TypeSpec.Builder?contentBuilder?=?TypeSpec.classBuilder("yourClassName")


2.MethodSpec.Builder


MethodSpec.Builder 是方法的构建类。



示例代码:


MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("yourMethodName")


3.FieldSpec.Builder


FieldSpec.Builder 是变量的构建类。



示例代码:


FieldSpec.Builder fieldBuilder = FieldSpec.builder(ClassName.get(field.getType()), "yourFieldName", Modifier.PRIVATE)


4.JavaFile.Builder



示例代码:


JavaFile javaFile = JavaFile.builder(classHolder.getPackageName(), contentBuilder.build())javaFile.writeTo(mFiler);


5.各类 Builder 的方法



6.代码生成示例


构造代码与生成结果示例 1:


for (FieldHolder field : fields) {FieldSpec f = FieldSpec.builder(ClassName.get(field.getType()), field.getName(), Modifier.PRIVATE).build();contentBuilder.addField(f);


private int id;private String name;private boolean is;


构造代码与生成结果示例 2:


MethodSpec.Builder buildMethodBuilder = MethodSpec.methodBuilder("build").addModifiers(Modifier.PUBLIC).returns(ClassName.get("android.content", "Intent"));


buildMethodBuilder.addParameter(ClassName.get("android.content", "Context"), "context");


buildMethodBuilder.addStatement(String.format("Intent intent = new Intent(context, %s.class)", classHolder.getClassName()));


for (FieldHolder field : fields) {buildMethodBuilder.addStatement(String.format("intent.putExtra("%s", %s)", field.getName(), field.getName()));}


buildMethodBuilder.addCode("if (!(context instanceof $T)) {\n", ClassName.get("android.app", "Activity"));


buildMethodBuilder.addStatement("intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)");


buildMethodBuilder.addCode("}\n");


buildMethodBuilder.addStatement("return intent");

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
如何在-Android-中完成一个-APT-项目的开发?,android文件下载实战