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

yilian發表於2020-02-20

前言

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

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

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、  FilerNameTypeMirror 物件
Elements:對  Element 物件進行操作
Filer:檔案操作介面,它可以建立  Java 檔案
Name:表示類名、方法名
TypeMirror:表示資料型別。如  intString、以及自定義資料型別
下面我們可以獲取被  @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();
        }
   }

例如:

變數: gendertype: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

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

最後生成的程式碼:

  /**
   * 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

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


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2676478/,如需轉載,請註明出處,否則將追究法律責任。

相關文章