前言
APT(Annotation Processor Tool)
是用來處理註解的,即註解處理器。APT
在編譯器會掃描處理源代碼中的註解,我們可以使用這些註解,然後利用 APT
自動生成 Java
代碼,減少模板代碼,提升編碼效率,使源碼更加簡潔,可讀性更高。
1、具體場景
下面我將會以項目中常見的 intent 頁面跳轉爲例,給大家演示一下,如何自動生成 intent
代碼,以及對getIntent
的參數自動賦值。
要實現上面這個功能我們需要了解
APT
、以及JavaPoet
。如果不太瞭解的同學可以先去了解一下。
常用寫法
Intent intent = new Intent(this,OtherActivity.class);
intent.putExtra("name",name);
intent.putExtra("gender",gender);
startActivity(intent);
數據獲取
String name = getIntent().getStringExtra("name",name);
String gender = getIntent().getStringExtra("gender",gender);
上述代碼很必要但重複性又很高,寫多了會煩,又浪費時間。並且在數據傳遞與獲取時 key
值都需要保持一致,這又需要我們新建很多的常量。所以,這裏我們希望上述的數據傳遞與獲取可以自動生成。
爲了實現這個需求,我們需要實現如下功能:
1)自動爲 OtherActivity
類生成一個叫做 OtherActivityAutoBundle
的類
2)使用建造者模式爲變量賦值
3)支持 startActivity
或 startActivityForResult
跳轉
4)支持調用一個方法即可解析 Intent
傳遞的數據,並賦值給跳轉的 Activity
中的變量
我們需要自動化如下代碼:
new OtherActivityAutoBundle()
.name("小明")
.gender("男")
.start(this);//或 startActivityForResult(this,requestCode)
在 OtherActivity 中,自動爲變量賦值:
new OtherActivityAutoBundle().bindIntentData(this,getIntent());
2、搭建 APT 項目
a、創建一個 Java Library,並創建註解類
例如:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface AutoBundle {
boolean exclude() default false;//不參與 intent、bundle 傳值
boolean addFlags() default false;//添加 activity 啓動方式
boolean isCloseFromActivity() default false;//是否關閉 FromActivity
boolean isBundle() default false;//是否使用 Bundle 對象傳值
boolean isSerializable() default false;//是否是 Serializable 類型
boolean isParcelable() default false;//是否是 Parcelable 類型
boolean isParcelableArray() default false;//是否是 ParcelableArray 類型
boolean isParcelableArrayList() default false;//是否是 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
public class AutoBundleProcessor extends AbstractProcessor {
}
在創建AutoBundleProcessor
後,我們需要重寫幾個方法
@Override
public synchronized void init(ProcessingEnvironment ev) {
}
在編譯開始時首先會回調此方法,在這裏,我們可以獲取一些實例爲後面做準備。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment rev) {
}
在該方法中,我們能夠獲取需要的類、變量、註解等相關信息,後面我們會利用這些來生成代碼
@Override
public Set<String> getSupportedAnnotationTypes() {
}
該方法中我們可以指定具體需要處理哪些註解
接着我們需要使用到 Elements
、 Filer
、Name
、TypeMirror
對象
**Elements:**對 Element
對象進行操作
**Filer:**文件操作接口,它可以創建 Java
文件
**Name:**表示類名、方法名
**TypeMirror:**表示數據類型。如 int
、String
、以及自定義數據類型
下面我們可以獲取被 @AutoBundle
註解元素的相關信息:
Set<? extends Element> elementsAnnotatedWith =
rev.getElementsAnnotatedWith(AutoBundle.class);
for (Element element : elementsAnnotatedWith) {
if (element.getKind() == ElementKind.FIELD) {
VariableElement variableElement = (VariableElement) element;
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
//類名
String className = typeElement.getSimpleName().toString();
//包名
String packageName =
mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();
AutoBundle autoBundle = variableElement.getAnnotation(AutoBundle.class);
//變量名
Name simpleName = variableElement.getSimpleName();
//變量類型
TypeMirror typeMirror = variableElement.asType();
}
}
例如:
變量:
gender
、type:java.lang.String
其他變量亦是如此。
現在我們需要新建類來保存上面獲取的值。這裏我們新建 FieldHolder
來保存變量類型、變量名以及其他信息。
FieldHolder
public class FieldHolder {
private String variableName;//變量名
private TypeMirror clazz;//字段類型(如:String)
private String packageName;//包名
private boolean addFlags;//是否是添加 activity 啓動方式
private boolean exclude;//是否參與 intent、bundle 傳值
private boolean closeFromActivity;//是否關閉當前 Activity
private boolean isBundle;//是否使用 Bundle 傳值
private boolean isSerializable;//是否實現 Serializable 接口的類
private boolean isParcelable;//是否是自定義類實現 Parcelable 接口
private boolean isParcelableArray;//是否是自定義類 ParcelableArray 類型
private boolean isParcelableArrayList;//是否是自定義類 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.Builder typeClass = TypeSpec.classBuilder(clazzName + "AutoBundle");
for (FieldHolder fieldHolder : fieldHolders) {
packageName = fieldHolder.getPackageName();
FieldSpec builder = FieldSpec.builder(ClassName.get(fieldHolder.getClazz()),
fieldHolder.getVariableName(), Modifier.PRIVATE).build();
typeClass.addField(builder);
MethodSpec.Builder buildParamMethod = 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());
}
生成的代碼:
public class OtherActivityAutoBundle {
private String name;
private String gender;
public OtherActivityAutoBundle name(String name) {
this.name = name;
return this;
}
public OtherActivityAutoBundle gender(String gender) {
this.gender = gender;
return this;
}
}
生成 start 方法
private void generateCommonStart(MethodSpec.Builder builderMethod, List<FieldHolder>
fieldHolders, String clazzName) {
builderMethod.addStatement(String.format("Intent intent = new Intent(context,%s.class)", clazzName));
/** 生成頁面跳轉方法 */
for (FieldHolder fieldHolder : fieldHolders) {
String fieldType = fieldHolder.getClazz().toString();
if ("android.os.Bundle".equals(fieldType)) {
builderMethod.addStatement(String.format("Bundle %s = new Bundle()", fieldHolder.getVariableName()));
builderMethod.addStatement(String.format("intent.putExtra(\"%s\",%s)", fieldHolder.getVariableName(), fieldHolder.getVariableName()));
mAutoBundleField = fieldHolder.getVariableName();
} else if (fieldHolder.isBundle() && String.class.getName().equals(fieldType)) {
builderMethod.addStatement(String.format("%s.putString(\"%s\",%s)", mAutoBundleField, fieldHolder.getVariableName(), fieldHolder.getVariableName()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
}
}
結果
public void start(Context context) {
Intent intent = new Intent(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 (FieldHolder fieldHolder : fieldHolders) {
packageName = fieldHolder.getPackageName();
TypeMirror clazz = fieldHolder.getClazz();
String fieldType = 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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
} else if ((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()));
}
}
生成的結果
public void bindIntentData(OtherActivity target, Intent intent) {
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 屬性的可訪問性
JavaFile javaFile = JavaFile.builder(packageName, typeClass.build())
.build();
try {
//生成 class 文件
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
生成的文件在 app/build/generated/ap_generated_sources/debug/out/包名/xxx
最後生成的代碼:
/**
* This codes are generated automatically. Do not modify! */
public class ThirdActivityAutoBundle {
private int no;
private String address;
private boolean isChoose;
public ThirdActivityAutoBundle no(int no) {
this.no=no;
return this;
}
public ThirdActivityAutoBundle address(String address) {
this.address=address;
return this;
}
public ThirdActivityAutoBundle isChoose(boolean isChoose) {
this.isChoose=isChoose;
return this;
}
public void start(Context context) {
Intent intent = new Intent(context,ThirdActivity.class);
intent.putExtra("no",no);
intent.putExtra("address",address);
intent.putExtra("isChoose",isChoose);
context.startActivity(intent);
}
public void startForResult(Context context, Integer requestCode) {
Intent intent = new Intent(context,ThirdActivity.class);
intent.putExtra("no",no);
intent.putExtra("address",address);
intent.putExtra("isChoose",isChoose);
((android.app.Activity)context).startActivityForResult(intent,requestCode);
}
public void bindIntentData(ThirdActivity target, Intent intent) {
target.no=intent.getIntExtra("no",0);
target.address=intent.getStringExtra("address");
target.isChoose = intent.getBooleanExtra("isChoose",false);
}
}
總結
好了,到這裏就全部結束了,大家可以發揮想象力添加自己想要的功能。
喜歡的話,點個讚唄
作爲一個程序員,要學的東西有很多,而學到的知識點,都是錢(因爲技術人員大部分情況是根據你的能力來定級、來發薪水的),技多不壓身。
爲了很好的生活,我們要多多學習,增加我們手裏的金錢。尤其經歷了這一疫情,我深深的感受到金錢的重要,所以,我們一定不能停下學習的腳步!
附上我的Android核心技術學習大綱,獲取相關內容來GitHub:https://github.com/Meng997998/AndroidJX