前言

APT(Annotation Processor Tool)是用来处理注解的,即注解处理器。APT在编译器会扫描处理源代码中的注解,我们可以使用这些注解,然后利用APT自动生成Java代码,减少模板代码,提升编码效率,使源码更加简洁,可读性更高。

1、具体场景

下面我将会以项目中常见的 intent 页面跳转为例,给大家演示一下,如何自动生成intent代码,以及对getIntent的参数自动赋值。

要实现上面这个功能我们需要了解APT、以及JavaPoet。如果不太了解的同学可以先去了解一下。

常用写法

Intentintent=newIntent(this,OtherActivity.class);intent.putExtra("name",name);intent.putExtra("gender",gender);startActivity(intent);

数据获取

Stringname=getIntent().getStringExtra("name",name);Stringgender=getIntent().getStringExtra("gender",gender);

上述代码很必要但重复性又很高,写多了会烦,又浪费时间。并且在数据传递与获取时key值都需要保持一致,这又需要我们新建很多的常量。所以,这里我们希望上述的数据传递与获取可以自动生成。

为了实现这个需求,我们需要实现如下功能:
1)自动为OtherActivity类生成一个叫做OtherActivityAutoBundle的类
2)使用建造者模式为变量赋值
3)支持startActivitystartActivityForResult跳转
4)支持调用一个方法即可解析Intent传递的数据,并赋值给跳转的Activity中的变量

我们需要自动化如下代码:

newOtherActivityAutoBundle().name("小明").gender("男").start(this);//或startActivityForResult(this,requestCode)

在 OtherActivity 中,自动为变量赋值:

newOtherActivityAutoBundle().bindIntentData(this,getIntent());2、搭建 APT 项目

a、创建一个 Java Library,并创建注解类
例如:

@Target(ElementType.FIELD)@Retention(RetentionPolicy.CLASS)public@interfaceAutoBundle{booleanexclude()defaultfalse;//不参与intent、bundle传值booleanaddFlags()defaultfalse;//添加activity启动方式booleanisCloseFromActivity()defaultfalse;//是否关闭FromActivitybooleanisBundle()defaultfalse;//是否使用Bundle对象传值booleanisSerializable()defaultfalse;//是否是Serializable类型booleanisParcelable()defaultfalse;//是否是Parcelable类型booleanisParcelableArray()defaultfalse;//是否是ParcelableArray类型booleanisParcelableArrayList()defaultfalse;//是否是ParcelableArrayList类型}

b、再创建一个 Java Library,并将上一步 Java Library 添加进来

此时,我们还需要在该 Library 中创建resources文件夹;接着在resources中创建META-INFservices两个文件夹;然后在services中创建一个名为javax.annotation.processing.Processor的文件。最后在该文件中写入我们注解处理器的全路径。

这里我们也可以使用自动化工具implementation 'com.google.auto.service:auto-service:1.0-rc2'感兴趣的去搜一下具体用法


3、创建自己的处理类,继承 AbstractProcessor

publicclassAutoBundleProcessorextendsAbstractProcessor{}

在创建AutoBundleProcessor后,我们需要重写几个方法

@Overridepublicsynchronizedvoidinit(ProcessingEnvironmentev){}

在编译开始时首先会回调此方法,在这里,我们可以获取一些实例为后面做准备。

@Overridepublicbooleanprocess(Set<?extendsTypeElement>set,RoundEnvironmentrev){}

在该方法中,我们能够获取需要的类、变量、注解等相关信息,后面我们会利用这些来生成代码

@OverridepublicSet<String>getSupportedAnnotationTypes(){}

该方法中我们可以指定具体需要处理哪些注解

接着我们需要使用到ElementsFilerNameTypeMirror对象
Elements:对Element对象进行操作
Filer:文件操作接口,它可以创建Java文件
Name:表示类名、方法名
TypeMirror:表示数据类型。如intString、以及自定义数据类型
下面我们可以获取被@AutoBundle注解元素的相关信息:

Set<?extendsElement>elementsAnnotatedWith=rev.getElementsAnnotatedWith(AutoBundle.class);for(Elementelement:elementsAnnotatedWith){if(element.getKind()==ElementKind.FIELD){VariableElementvariableElement=(VariableElement)element;TypeElementtypeElement=(TypeElement)variableElement.getEnclosingElement();//类名StringclassName=typeElement.getSimpleName().toString();//包名StringpackageName=mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();AutoBundleautoBundle=variableElement.getAnnotation(AutoBundle.class);//变量名NamesimpleName=variableElement.getSimpleName();//变量类型TypeMirrortypeMirror=variableElement.asType();}}

例如:

变量:gendertype:java.lang.String

其他变量亦是如此。

现在我们需要新建类来保存上面获取的值。这里我们新建FieldHolder来保存变量类型、变量名以及其他信息。
FieldHolder

publicclassFieldHolder{privateStringvariableName;//变量名privateTypeMirrorclazz;//字段类型(如:String)privateStringpackageName;//包名privatebooleanaddFlags;//是否是添加activity启动方式privatebooleanexclude;//是否参与intent、bundle传值privatebooleancloseFromActivity;//是否关闭当前ActivityprivatebooleanisBundle;//是否使用Bundle传值privatebooleanisSerializable;//是否实现Serializable接口的类privatebooleanisParcelable;//是否是自定义类实现Parcelable接口privatebooleanisParcelableArray;//是否是自定义类ParcelableArray类型privatebooleanisParcelableArrayList;//是否是自定义类ParcelableArrayList类型}4、下面我们需要使用 JavaPoet 生成 Java 文件简单介绍下需要用到的 API

A、TypeSpec.Builder

主要用于生成类,这里的类包括的范围比较广,可以是一个class、一个interface等等。

方法功能classBuilder生成类interfaceBuilder生成接口

B、MethodSpec.Builder

主要用于生成类

方法功能constructBuilder生成构造方法methodBuilder生成成员方法

C、FieldSpec.Builder

主要用于生成成员变量

方法功能builder生成一个成员变量

D、JavaFile.Builder

主要用来生成 Java 文件

方法功能builder生成一个 JavaFile 对象writeTo将数据写到 Java 文件中

E、其他方法

方法功能描述addModifier添加修饰符比如:public、private、static 等等addParameter添加参数向方法中添加参数。例:addParameter(ClassName.get("包名"),"类名")addStatement添加陈述直接添加代码。例:addStatement("return this")addCode添加代码语句直接添加代码,自动帮你导入需要的包,并在末尾自动添加分号returns添加返回值为方法添加返回值。例:returns(void.class)addMethod添加方法将生成的方法添加到类中。例:addMethod(customMethod.build())addField添加变量将生成的变量添加到类中。例:addField(customField.build())

生成成员变量以及变量的 set 方法

TypeSpec.BuildertypeClass=TypeSpec.classBuilder(clazzName+"AutoBundle");for(FieldHolderfieldHolder:fieldHolders){packageName=fieldHolder.getPackageName();FieldSpecbuilder=FieldSpec.builder(ClassName.get(fieldHolder.getClazz()),fieldHolder.getVariableName(),Modifier.PRIVATE).build();typeClass.addField(builder);MethodSpec.BuilderbuildParamMethod=MethodSpec.methodBuilder(String.format("%s",fieldHolder.getVariableName()));buildParamMethod.addParameter(ClassName.get(fieldHolder.getClazz()),fieldHolder.getVariableName());buildParamMethod.addStatement(String.format("this.%s=%s",fieldHolder.getVariableName(),fieldHolder.getVariableName()));buildParamMethod.addStatement(String.format("return%s","this"));buildParamMethod.addModifiers(Modifier.PUBLIC);buildParamMethod.returns(ClassName.get(fieldHolder.getPackageName(),clazzName+"AutoBundle"));typeClass.addMethod(buildParamMethod.build());}

生成的代码:

publicclassOtherActivityAutoBundle{privateStringname;privateStringgender;publicOtherActivityAutoBundlename(Stringname){this.name=name;returnthis;}publicOtherActivityAutoBundlegender(Stringgender){this.gender=gender;returnthis;}}

生成 start 方法

privatevoidgenerateCommonStart(MethodSpec.BuilderbuilderMethod,List<FieldHolder>fieldHolders,StringclazzName){builderMethod.addStatement(String.format("Intentintent=newIntent(context,%s.class)",clazzName));/**生成页面跳转方法*/for(FieldHolderfieldHolder:fieldHolders){StringfieldType=fieldHolder.getClazz().toString();if("android.os.Bundle".equals(fieldType)){builderMethod.addStatement(String.format("Bundle%s=newBundle()",fieldHolder.getVariableName()));builderMethod.addStatement(String.format("intent.putExtra(\"%s\",%s)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));mAutoBundleField=fieldHolder.getVariableName();}elseif(fieldHolder.isBundle()&&String.class.getName().equals(fieldType)){builderMethod.addStatement(String.format("%s.putString(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((boolean.class.getName().equals(fieldType)||Boolean.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putBoolean(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((byte.class.getName().equals(fieldType)||Byte.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putByte(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((char.class.getName().equals(fieldType)||Character.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putChar(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((short.class.getName().equals(fieldType)||Short.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putShort(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((int.class.getName().equals(fieldType)||Integer.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putInt(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((long.class.getName().equals(fieldType)||Long.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putLong(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((float.class.getName().equals(fieldType)||Float.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putFloat(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((double.class.getName().equals(fieldType)||Double.class.getName().equals(fieldType))&&fieldHolder.isBundle()){builderMethod.addStatement(String.format("%s.putDouble(\"%s\",%s)",mAutoBundleField,fieldHolder.getVariableName(),fieldHolder.getVariableName()));}}

结果

publicvoidstart(Contextcontext){Intentintent=newIntent(context,OtherActivity.class);intent.putExtra("id",id);intent.putExtra("name",name);intent.putExtra("is",is);intent.putExtra("mByte",mByte);intent.putExtra("b",b);intent.putExtra("mShort",mShort);intent.putExtra("mLong",mLong);intent.putExtra("mFloat",mFloat);intent.putExtra("mDouble",mDouble);context.startActivity(intent);}

生成 bindIntentData

for(FieldHolderfieldHolder:fieldHolders){packageName=fieldHolder.getPackageName();TypeMirrorclazz=fieldHolder.getClazz();StringfieldType=clazz.toString();if((boolean.class.getName().equals(fieldType)||Boolean.class.getName().equals(fieldType))&&!fieldHolder.isBundle()&&!fieldHolder.isExclude()){bindIntentMethod.addStatement(String.format("target.%s=intent.getBooleanExtra(\"%s\",false)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((byte.class.getName().equals(fieldType)||Byte.class.getName().equals(fieldType))&&!fieldHolder.isBundle()){bindIntentMethod.addStatement(String.format("target.%s=intent.getByteExtra(\"%s\",(byte)0)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((char.class.getName().equals(fieldType)||Character.class.getName().equals(fieldType))&&!fieldHolder.isBundle()){bindIntentMethod.addStatement(String.format("target.%s=intent.getCharExtra(\"%s\",(char)0)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((short.class.getName().equals(fieldType)||Short.class.getName().equals(fieldType))&&!fieldHolder.isBundle()){bindIntentMethod.addStatement(String.format("target.%s=intent.getShortExtra(\"%s\",(short)0)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((int.class.getName().equals(fieldType)||Integer.class.getName().equals(fieldType))&&!fieldHolder.isBundle()&&!fieldHolder.isExclude()){bindIntentMethod.addStatement(String.format("target.%s=intent.getIntExtra(\"%s\",0)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((long.class.getName().equals(fieldType)||Long.class.getName().equals(fieldType))&&!fieldHolder.isBundle()){bindIntentMethod.addStatement(String.format("target.%s=intent.getLongExtra(\"%s\",0)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((float.class.getName().equals(fieldType)||Float.class.getName().equals(fieldType))&&!fieldHolder.isBundle()){bindIntentMethod.addStatement(String.format("target.%s=intent.getFloatExtra(\"%s\",0)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}elseif((double.class.getName().equals(fieldType)||Double.class.getName().equals(fieldType))&&!fieldHolder.isBundle()){bindIntentMethod.addStatement(String.format("target.%s=intent.getDoubleExtra(\"%s\",0)",fieldHolder.getVariableName(),fieldHolder.getVariableName()));}}

生成的结果

publicvoidbindIntentData(OtherActivitytarget,Intentintent){target.id=intent.getIntExtra("id",0);target.name=intent.getStringExtra("name");target.is=intent.getBooleanExtra("is",false);target.mByte=intent.getByteExtra("mByte",(byte)0);target.b=intent.getCharExtra("b",(char)0);target.mShort=intent.getShortExtra("mShort",(short)0);target.mLong=intent.getLongExtra("mLong",0);target.mFloat=intent.getFloatExtra("mFloat",0);target.mDouble=intent.getDoubleExtra("mDouble",0);}

最后将生成好的 Java 代码写入文件

//与目标Class放在同一个包下,解决Class属性的可访问性JavaFilejavaFile=JavaFile.builder(packageName,typeClass.build()).build();try{//生成class文件javaFile.writeTo(mFiler);}catch(IOExceptione){e.printStackTrace();}

生成的文件在app/build/generated/ap_generated_sources/debug/out/包名/xxx


最后生成的代码:

/***Thiscodesaregeneratedautomatically.Donotmodify!*/publicclassThirdActivityAutoBundle{privateintno;privateStringaddress;privatebooleanisChoose;publicThirdActivityAutoBundleno(intno){this.no=no;returnthis;}publicThirdActivityAutoBundleaddress(Stringaddress){this.address=address;returnthis;}publicThirdActivityAutoBundleisChoose(booleanisChoose){this.isChoose=isChoose;returnthis;}publicvoidstart(Contextcontext){Intentintent=newIntent(context,ThirdActivity.class);intent.putExtra("no",no);intent.putExtra("address",address);intent.putExtra("isChoose",isChoose);context.startActivity(intent);}publicvoidstartForResult(Contextcontext,IntegerrequestCode){Intentintent=newIntent(context,ThirdActivity.class);intent.putExtra("no",no);intent.putExtra("address",address);intent.putExtra("isChoose",isChoose);((android.app.Activity)context).startActivityForResult(intent,requestCode);}publicvoidbindIntentData(ThirdActivitytarget,Intentintent){target.no=intent.getIntExtra("no",0);target.address=intent.getStringExtra("address");target.isChoose=intent.getBooleanExtra("isChoose",false);}}总结

好了,到这里就全部结束了,大家可以发挥想象力添加自己想要的功能。
喜欢的话,点个赞呗

作为一个程序员,要学的东西有很多,而学到的知识点,都是钱(因为技术人员大部分情况是根据你的能力来定级、来发薪水的),技多不压身。

为了很好的生活,我们要多多学习,增加我们手里的金钱。尤其经历了这一疫情,我深深的感受到金钱的重要,所以,我们一定不能停下学习的脚步!

附上我的Android核心技术学习大纲,获取相关内容来GitHub:https://github.com/Meng997998/AndroidJX
vx:xx13414521