3000行代碼怎樣簡化成300行?來,一文來教你

前言

APT(Annotation Processor Tool)是用來處理註解的,即註解處理器。APT 在編譯器會掃描處理源代碼中的註解,我們可以使用這些註解,然後利用 APT自動生成 Java代碼,減少模板代碼,提升編碼效率,使源碼更加簡潔,可讀性更高。

1、具體場景

下面我將會以項目中常見的 intent 頁面跳轉為例,給大家演示一下,如何自動生成 intent代碼,以及對getIntent的參數自動賦值。

要實現上面這個功能我們需要了解 APT、以及JavaPoet。如果不太瞭解的同學可以先去了解一下。

常用寫法

<code>  Intent intent = new Intent(this,OtherActivity.class);  intent.putExtra("name",name);  intent.putExtra("gender",gender);  startActivity(intent);/<code>

數據獲取

<code>  String name = getIntent().getStringExtra("name",name);  String gender = getIntent().getStringExtra("gender",gender);/<code>

上述代碼很必要但重複性又很高,寫多了會煩,又浪費時間。並且在數據傳遞與獲取時 key 值都需要保持一致,這又需要我們新建很多的常量。所以,這裡我們希望上述的數據傳遞與獲取可以自動生成。

為了實現這個需求,我們需要實現如下功能:1)自動為 OtherActivity類生成一個叫做 OtherActivityAutoBundle 的類2)使用建造者模式為變量賦值3)支持 startActivity 或 startActivityForResult 跳轉4)支持調用一個方法即可解析 Intent 傳遞的數據,並賦值給跳轉的 Activity 中的變量

我們需要自動化如下代碼:

<code>  new OtherActivityAutoBundle()          .name("小明")          .gender("男")          .start(this);//或 startActivityForResult(this,requestCode)/<code>

在 OtherActivity 中,自動為變量賦值:

<code>  new OtherActivityAutoBundle().bindIntentData(this,getIntent());/<code>

2、搭建 APT 項目

a、創建一個 Java Library,並創建註解類例如:

<code>  @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 類型  }/<code>

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' 感興趣的去搜一下具體用法

3000行代碼怎樣簡化成300行?來,一文來教你

3、創建自己的處理類,繼承 AbstractProcessor

<code>  public class AutoBundleProcessor extends AbstractProcessor {  }/<code>

在創建AutoBundleProcessor 後,我們需要重寫幾個方法

<code>   @Override   public synchronized void init(ProcessingEnvironment ev) {   }/<code>

在編譯開始時首先會回調此方法,在這裡,我們可以獲取一些實例為後面做準備。

<code>  @Override  public boolean process(Set extends TypeElement> set, RoundEnvironment rev) {  }/<code>

在該方法中,我們能夠獲取需要的類、變量、註解等相關信息,後面我們會利用這些來生成代碼

<code>  @Override  public Set<string> getSupportedAnnotationTypes() {  }/<string>/<code>

該方法中我們可以指定具體需要處理哪些註解

接著我們需要使用到 Elements、 Filer、Name、TypeMirror 對象Elements:對 Element 對象進行操作Filer:文件操作接口,它可以創建 Java 文件Name:表示類名、方法名TypeMirror:表示數據類型。如 int、String、以及自定義數據類型下面我們可以獲取被 @AutoBundle 註解元素的相關信息:

<code>  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();        }   }/<code>

例如:

變量:gender、type:java.lang.String

其他變量亦是如此。

現在我們需要新建類來保存上面獲取的值。這裡我們新建 FieldHolder 來保存變量類型、變量名以及其他信息。FieldHolder

<code>  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 類型  }/<code>

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 方法

<code>  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());  }/<code>

生成的代碼:

<code>  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;    }  }/<code>

生成 start 方法

<code>  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()));              }   }/<fieldholder>/<code>

結果

<code>  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);    }/<code>

生成 bindIntentData

<code>  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()));                  }   }/<code>

生成的結果

<code>  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);  }/<code>

最後將生成好的 Java 代碼寫入文件

<code>   //與目標 Class 放在同一個包下,解決 Class 屬性的可訪問性   JavaFile javaFile = JavaFile.builder(packageName, typeClass.build())             .build();    try {           //生成 class 文件           javaFile.writeTo(mFiler);         } catch (IOException e) {             e.printStackTrace();       }/<code>

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

3000行代碼怎樣簡化成300行?來,一文來教你

最後生成的代碼:

<code>  /**   * 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);    }  }/<code>

總結

好了,到這裡就全部結束了,大家可以發揮想象力添加自己想要的功能。喜歡的話,點個讚唄

作為一個程序員,要學的東西有很多,而學到的知識點,都是錢(因為技術人員大部分情況是根據你的能力來定級、來發薪水的),技多不壓身。

為了很好的生活,我們要多多學習,增加我們手裡的金錢。尤其經歷了這一疫情,我深深的感受到金錢的重要,所以,我們一定不能停下學習的腳步!

附上我的Android核心技術學習大綱,獲取相關內容來私信我【學習PDF】

3000行代碼怎樣簡化成300行?來,一文來教你


分享到:


相關文章: