3000行程式碼怎樣簡化成300行?來,一文來教你!
前言
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:
vx:xx13414521
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2676478/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 程式是怎樣跑起來的
- Go 程式是怎樣跑起來的Go
- 《程式是怎樣跑起來的》第二章
- 《程式是怎樣跑起來的》第五章
- 《程式是怎樣跑起來的》第七章
- 《程式是怎樣跑起來的》第十一章
- 那些簡歷造假拿 Offer 的程式設計師,後來都怎麼樣了?程式設計師
- 程式是怎樣跑起來的第二章有感
- 《程式是怎樣跑起來的》第一章
- 《程式是怎樣跑起來的》第六章
- 《程式是怎樣跑起來的》第九章
- 《程式是怎樣跑起來的》第八章
- 《程式是怎樣跑起來的》第十章
- 「譯」使用策略設計模式來簡化程式碼設計模式
- 程式碼來構建一個簡單的compilerCompile
- 《程式是怎樣跑起來的》,計算機程式很複雜嗎?計算機
- 怎樣使你的 JavaScript 程式碼簡單易讀JavaScript
- 程式是怎樣跑起來的第一章有感
- 榮耀V40怎麼樣?簡單來說說體驗!
- 用異常處理來精簡你的程式碼
- 《程式是怎樣跑起來的》讀書筆記1——對程式設計師來說CPU是什麼筆記程式設計師
- 一文教你預測 app 未來的變現情況APP
- Flink CDC 3.0 耍起來到底怎麼樣?
- 5G來了,AR會怎樣?
- 計算機是怎樣跑起來的計算機
- 執行效率高的程式碼-可以這樣寫出來~
- 使用 Async / Await 來編寫簡明的非同步程式碼AI非同步
- [譯] 一文教你預測 app 未來的變現情況APP
- LevelDB 程式碼擼起來!
- 藉助ai來分析程式碼,理解程式碼AI
- 那些拼命加班的程式設計師們,後來都怎麼樣了?程式設計師
- 那些把公司當家的程式設計師,後來怎麼樣了?程式設計師
- 怎樣提升一個產品經理的技能,我來簡單說5點
- redux樣板程式碼簡化寫法Redux
- 手把手教你怎樣用Python生成漂亮且精闢的影像(附教程程式碼)Python
- 餐飲小程式怎麼樣可以更好的推廣來獲取客戶?
- 工業人工智慧的未來會怎樣?人工智慧
- 《Spelunky 2》怎樣用AI來測試關卡?AI