深圳朝阳电子网站建设,wordpress 数据库类型,网站建设汇编材料,邢台做移动网站的公司前言
APT(Annotation Processing Tool)即注解处理器#xff0c;是一种处理注解的工具。
APT在编译时期扫描处理源代码中的注解#xff0c;开发中可以根据注解#xff0c;利用APT自动生成Java代码#xff0c;减少冗余的代码和手动的代码输入过程#xff0c;提升了编码效率…前言
APT(Annotation Processing Tool)即注解处理器是一种处理注解的工具。
APT在编译时期扫描处理源代码中的注解开发中可以根据注解利用APT自动生成Java代码减少冗余的代码和手动的代码输入过程提升了编码效率同时使源代码看起来更清晰简洁可读性提升。
目前很多第三方开源框架采用APT技术以减少开发者的重复工作。常见的如ButterKnife、EventBus等。
本文侧重于实际应用的讲解以Android APP开发过程中一个常见的页面跳转场景为示例从搭建项目、APT数据与功能介绍、提取数据和自动化生成代码几个过程逐步讲解如何完成一个APT项目的开发。 APT的概念
APT即注解处理器(Annotation Processor Tool)是javac内置的一个用于编译时扫描和处理注解的工具。简单的说在源代码编译阶段通过注解处理器我们可以获取源文件内注解相关内容。
由于注解处理器可以在程序编译阶段工作所以我们可以在编译期间通过注解处理器进行我们需要的操作。比较常用的用法就是在编译期间获取相关注解数据然后动态生成代码源文件。
通常注解处理器是用于自动产生一些有规律性的重复代码解决了手工编写重复代码的问题大大提升编码效率。
目前很多比较著名的开源框架使用了此技术如ButterKnife为开发人员解决了手动编写大量findViewById方法的问题。其它如GreenDao中使用的JDT与APT思想完全一致只是IDE与工具不同。
使用场景举例
1.需求场景
在Android开发中Activity的跳转是必不可少的操作。当需要通过Intent传递数据的时候代码一般是如下所示
Intent intent new Intent(context, TestActivity.class);
intent.putExtra(id, id);
intent.putExtra(name, name);
intent.putExtra(is, is);
if (!(context instanceof Activity)) {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
startActivity(intent);以及在TestActivity中的获取数据操作
id intent.getIntExtra(id, 0);
name intent.getStringExtra(name);
is intent.getBooleanExtra(is, false);以上代码的问题在于对于每个过程都需要编写类似的代码重复性比较大浪费时间。
在数据传递和解析时key需要保持一致。我们可以用常量来代替但将定义很多常量。
我们希望以上代码可以自动化生成开发者只需要调用几个可读性更好的方法即可实现上述过程。
2.分析
针对这个需求场景我们需要实现的自动化功能如下
1自动为TestActivity生成一个类叫做TestActivityFastBundle 2提供构造者模式的链式调用可以为需要的变量赋值 3提供一个build方法可以返回一个Intent对象 4可以跳转到Activity支持startActivity或startActivityForResult 5支持调用一个接口解析Intent中传递的数据并赋值给Activity。
我们期望简化后调用时候是这样的这将跳转到TestActivity
new TestActivityFastBundle().id(1).is(true).name(user).launch(this); // 或者使用launchForResult在TestActivity中我们期望调用
new TestActivityFastBundle().bind(this, getIntent());实现自动将Intent中的变量赋值给当前类中的变量。
搭建APT项目
1.创建一个Android Library并创建自己需要的注解类。
举例
Retention(CLASS)
Target(FIELD)
public interface AutoBundle {boolean require() default false;
}2.创建一个Java Library引用步骤1中所创建的Android Library并为这个Java Library添加依赖。
implementation com.google.auto.service:auto-service:1.0-rc2介绍一下这个库是做什么用的
因为注解处理器是在编译期间进行工作需要向编译器进行“注册”让编译器知道需要使用哪个注解器处理数据。
如果不使用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子类后我们需要重写其中的几个方法来实现自己的处理逻辑
Override
public synchronized void init(ProcessingEnvironment processingEnvironment)Processor的初始化方法在编译阶段会首先回调此方法ProcessingEnvironment类包含了解析需要的数据对象我们可以通过它获取到一系列我们需要的其他对象进而获取到需要的数据。
Override
public boolean process(Set? extends TypeElement set, RoundEnvironment roundEnvironment)process方法在编译过程中回调在此我们可以获取到我们需要的类、对象及其对应的注解在此可以分析并处理数据最终生成我们需要的代码。
Override
public SetString 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子类它表示这个元素是一个类或者接口。当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中的变量上设置注解
AutoBundle
public int id;
AutoBundle
public String name;
AutoBundle
public boolean is;其中AutoBundle注解是我们自己定义的注解类。
初步设计好后我们需要在process方法中重写我们的逻辑
Override
public 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 true
name “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);contentBuilder.addMethod(buildMethodBuilder.build());public Intent build(Context context) {Intent intent new Intent(context, TestActivity.class);intent.putExtra(id, id);intent.putExtra(name, name);intent.putExtra(is, is);if (!(context instanceof Activity)) {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}return intent;
}构造代码与生成结果示例3
String fieldTypeName field.getType().toString();3if (int.class.getName().equals(fieldTypeName)|| Integer.class.getName().equals(fieldTypeName)) {builder.addStatement(String.format(target.%s intent.getIntExtra(\%s\, 0), field.getName(), field.getName()));} else if (String.class.getName().equals(fieldTypeName)) {builder.addStatement(String.format(target.%s intent.getStringExtra(\%s\), field.getName(), field.getName()));} else if (boolean.class.getName().equals(fieldTypeName)|| Boolean.class.getName().equals(fieldTypeName)) {builder.addStatement(String.format(target.%s intent.getBooleanExtra(\%s\, false), field.getName(), field.getName()));}public void bind(TestActivity target, Intent intent) {target.id intent.getIntExtra(id, 0);target.name intent.getStringExtra(name);target.is intent.getBooleanExtra(is, false);
}7.将生成好的代码写入文件
JavaFile javaFile JavaFile.builder(classHolder.getPackageName(), contentBuilder.build()).build();try {javaFile.writeTo(mFiler);
} catch (IOException e) {e.printStackTrace();
}构建一个JavaFile对象将构造好的TypeSpecBuilder内容放入并写入到Filer中即可。编译后此类文件便生成在对应包下如图所示自动生成文件在build/generated/source/kapt下使用kapt指令编译。 生成代码 更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》 1.Android车载应用开发系统学习指南附项目实战
2.Android Framework学习指南助力成为系统级开发高手
3.2023最新Android中高级面试题汇总解析告别零offer
4.企业级Android音视频开发学习路线项目实战附源码
5.Android Jetpack从入门到精通构建高质量UI界面
6.Flutter技术解析与实战跨平台首要之选
7.Kotlin从入门到实战全方面提升架构基础
8.高级Android插件化与组件化含实战教程和源码
9.Android 性能优化实战360°全方面性能调优
10.Android零基础入门到精通高手进阶之路
敲代码不易关注一下吧。ღ( ´ᴗ )