3000行代码怎样简化成300行?来,一文来教你!
APT(Annotation Processor Tool)
是用来处理注解的,即注解处理器。APT
在编译器会扫描处理源代码中的注解,我们可以使用这些注解,然后利用APT
自动生成Java
代码,减少模板代码,提升编码效率,使源码更加简洁,可读性更高。
下面我将会以项目中常见的 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)支持startActivity
或startActivityForResult
跳转
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-INF
和services
两个文件夹;然后在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(){}
该方法中我们可以指定具体需要处理哪些注解
接着我们需要使用到Elements
、Filer
、Name
、TypeMirror
对象
Elements:对Element
对象进行操作
Filer:文件操作接口,它可以创建Java
文件
Name:表示类名、方法名
TypeMirror:表示数据类型。如int
、String
、以及自定义数据类型
下面我们可以获取被@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();}}
例如:
变量:
gender
、type: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
等等。
B、MethodSpec.Builder
主要用于生成类
C、FieldSpec.Builder
主要用于生成成员变量
D、JavaFile.Builder
主要用来生成 Java 文件
E、其他方法
生成成员变量以及变量的 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
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。