19

Android 中使用 APT 简化代码

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ%3D%3D&%3Bmid=2247489705&%3Bidx=1&%3Bsn=8ba4bd1dd1f4bd43d7fb1ccc18cec7a7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

code小生 一个专注大前端领域的技术平台 公众号回复 Android 加入安卓技术群

作者:Kotyo

链接:https://www.jianshu.com/p/8846406c3b51

声明:本文已获 Kotyo 授权发表,转发等请联系原作者授权

前言

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

YFreEzB.jpg!web image.png

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:表示数据类型。如 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 等等。

3A3I7vb.png!web

B、MethodSpec.Builder

主要用于生成类

nYBZBvq.png!web

C、FieldSpec.Builder

主要用于生成成员变量

3AjeEzJ.png!web

D、JavaFile.Builder

主要用来生成 Java 文件

BZviInE.png!web

E、其他方法

aIBBfuN.jpg!web

生成成员变量以及变量的 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

BJZr2mM.png!web image.png

最后生成的代码:

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

好了,到这里就全部结束了,大家可以发挥想象力添加自己想要的功能。有什么问题的话,可以给我提 issue

变量有注释,配合代码中的示例,还是比较容易理解的

代码地址

https://github.com/androidxiao/AutoBundleSample/tree/master


Recommend

  • 9
    • jiajunhuang.com 3 years ago
    • Cache

    Go使用闭包简化数据库操作代码

    Go使用闭包简化数据库操作代码 在日常工程中,我们可能要开事务来完成一些操作,因此就会有如下代码: // UpdateTaskRemark 更新任务备注 func UpdateTaskRemark(taskID uint32, remark string) error { sql, args, err := s...

  • 7

    最近在对公有云服务器进行梳理和资源整合,难免要进行机器初始化,以及针对部分镜像进行重新构建。 在这个过程中,最浪费时间的莫过于软件包的下载,为了提升整体部署和镜像构建效率,可以搭建一台用于缓存各种常用软件包的缓存代理服务器。 虽...

  • 7
    • www.lujun9972.win 3 years ago
    • Cache

    Ubuntu使用apt安装本地deb软件包

    Ubuntu使用apt安装本地deb软件包 平常都是用 dpkg -i 来安装本地deb软件包。但是 dpkg 不会自动安装依赖包,因此安装很容易出现依赖相关的错误。 之后需要通过运行 sudo apt-get install -f 来安装依赖 ...

  • 4
    • www.androidchina.net 2 years ago
    • Cache

    Android Studio中使用apt

    Android Studio中使用apt 你还在对着枯燥的重复代码一味复制粘贴吗?这样跟搬砖有何区别?你是否曾想过:你用代码编写出一个自动化的APP,但为何代码本身却缺少了活力?掌握Android-apt,杜绝重复代...

  • 9
    • blog.gslin.org 2 years ago
    • Cache

    APT 不使用 HTTPS 的說明

    APT 不使用 HTTPS 的說明 居然有個獨立的網站在說明:「Why does APT not use HTTPS?」。主要是 HTTPS 沒有增加太多保護,但會使得維護的複雜度變高很多。 首先是被竄改的問題,

  • 4

    在这篇文章中,我将与你分享一些关于JS的技巧,可以提高你的JS技能。作者:CodeOz译者:前端小智 来源:dev有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。本...

  • 2
    • blog.lucien.ink 2 years ago
    • Cache

    Debian 10 apt 使用阿里云镜像

    请注意,本文编写于 491 天前,最后修改于 491 天前,其中某些信息可能已经过时。 Debian 10 apt 使用阿里云镜像本文地址:blog.lucien.ink/archives/513

  • 3

    在 Ubuntu 中使用 apt 命令列出可升级的软件包-51CTO.COM 在 Ubuntu 中使用 apt 命令列出可升级的软件包 作者:Abhishek Prakash 2022-07-25 10:20:39

  • 3

    Java使用lamda表达式简化代码 代码,自然写的越简洁越好啦,写的人舒服,看的人也舒服,一切为了高效。

  • 7
    • www.myfreax.com 1 year ago
    • Cache

    如何在Linux 使用apt命令 | myfreax

    apt Linux apt命令 apt是一个命令行程序,用于在基于Debian的Linux发行版安装,更新,删除和管理软件 ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK