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)支持 startActivitystartActivityForResult 跳轉
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-INFservices 兩個文件夾;然後在 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() {
  }

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

接着我們需要使用到 ElementsFilerNameTypeMirror 對象
**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

最後生成的代碼:

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

發佈了120 篇原創文章 · 獲贊 59 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章