第三章 APT之生成接口管理类

上一章已经完成了简单的HelloWorld生成,这章的主要目的是生成下面的一个类,这个类主要是为Retrofit创建api接口实现,并提供全局唯一的api调用。

当然这里我是有很多不同的api并且属于不同的ip下的,如果你的api都属于一个ip或者说属于域名下,那么可能不需要这样的写法。

主要还是演示一下稍微复杂一些的的类应该怎样生成。

public class APIServiceManager { //自动生成好的类
  private static ApiService apiService;

  private static BreadtripAPI breadtripAPI;

  private static QyerGuideAPI qyerGuideAPI;

  private static TravelNotesAPI travelNotesAPI;

  public static ApiService getApiService() {
    return apiService==null ?
                HttpServiceManager.getInstance().getRetrofit().create(ApiService.class)
                :apiService;
  }

  public static BreadtripAPI getBreadtripAPI() {
    return breadtripAPI==null ?
                HttpServiceManager.getInstance().getRetrofit().create(BreadtripAPI.class)
                :breadtripAPI;
  }

  public static QyerGuideAPI getQyerGuideAPI() {
    return qyerGuideAPI==null ?
                HttpServiceManager.getInstance().getRetrofit().create(QyerGuideAPI.class)
                :qyerGuideAPI;
  }

  public static TravelNotesAPI getTravelNotesAPI() {
    return travelNotesAPI==null ?
                HttpServiceManager.getInstance().getRetrofit().create(TravelNotesAPI.class)
                :travelNotesAPI;
  }
}

第一步

使用上一章生成helloworld的方式,先硬生成一个APIServiceManager class

在我使用apt时发现了一个问题,如果你正确的生成了文件,那么如果你改动了代码,再次生成的时候需要Rebuild Project,但是如果这次的文件生成后报错,那么再次生成只需要点一下Run旁边的小锤子

Paste_Image.png

也就是Make就可以了,所以我发现了个比较笨的调试方式,生成文件时故意搞错某个地方,那么下次生成就比较方便了

ok 那么就先生成一个错误的APIServiceManager class

    final String CLASS_NAME="GAPIServiceManager";//这里我已经有了APIServiceManager class,所以为了演示和避免同名就随便加了个字符
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        MethodSpec main = MethodSpec.methodBuilder("main")
                .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
                .returns(String.class)//这里原来是返回空值,为了让类报错返回值设置成String
                .addParameter(String[].class,"arg")
                .addStatement("$T.out.println($S)",System.class,"Hello world")
                .build();
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//这里是类名也是java文件名,这里要改动成下面的样子。
        TypeSpec helloWorld = TypeSpec.classBuilder(CLASS_NAME)
                .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                .addMethod(main)
                .build();
        JavaFile javaFile =JavaFile
                .builder("gear.yc.com.gearapplication.network",helloWorld)//这里的目录名是我自己项目的,后面会优化成获取注解类的类名,暂时写死。
                .build();
        try {
            javaFile.writeTo(mFiler);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

生成后的文件

当然肯定会报错,要的就是这个效果。

public final class GAPIServiceManager {
  public static String main(String[] arg) {
    System.out.println("Hello world");
  }
}

第二步

生成类的变量

FieldSpec fieldSpec =FieldSpec.builder(String.class,"apiService")//因为现在还获取不到类型所以先用String代替
                .addModifiers(Modifier.PRIVATE,Modifier.STATIC)
                .build();

TypeSpec helloWorld = TypeSpec.classBuilder(CLASS_NAME)
                .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                .addField(fieldSpec)
                .addMethod(main)
                .build();

生成后的文件

public final class GAPIServiceManager {
  private static String apiService;//已经生成了一个很像的变量

  public static String main(String[] arg) {
    System.out.println("Hello world");
  }
}

可能会有多个参数那么就需要循环生成

List<FieldSpec> fieldSpecs =new ArrayList<>();
        for (int i = 0; i <3 ; i++) {//先随便生成几个
            FieldSpec fieldSpec =FieldSpec.builder(String.class,"apiService"+i)
                    .addModifiers(Modifier.PRIVATE,Modifier.STATIC)
                    .build();
            fieldSpecs.add(fieldSpec);
        }

TypeSpec helloWorld = TypeSpec.classBuilder(CLASS_NAME)
                .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                .addFields(fieldSpecs)//这里要改成添加多个的哦
                .addMethod(main)
                .build();

生成后的文件

public final class GAPIServiceManager {
  private static String apiService0;//有好几个比较像的了

  private static String apiService1;

  private static String apiService2;

  public static String main(String[] arg) {
    System.out.println("Hello world");
  }
}

那么怎样来获取我们想要类型呢,最重要的来了,当然是用上一章添加的注解了...

在api接口上加上注解

有几个这种接口就加几个

@com.gear.apifinder.annotation.APIService
public interface ApiService {

前面代码中已经指定过需要的注解有哪些

@Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types=new LinkedHashSet<>();
        types.add(APIManager.class.getCanonicalName());
        types.add(APIService.class.getCanonicalName());
        return types;
    }

//roundEnv中会有这些被注解的类的element,只需要把他们拿出来使用就可以
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 

获取带有APIService的类

List<Element> mServiceElements=new ArrayList<>();//因为有多个类需要容器
    private void processOnAPIService(RoundEnvironment environment){
        for (Element element : environment.getElementsAnnotatedWith(APIService.class)) {
            mServiceElements.add(element);
        }
        if(mServiceElements.size()==0){//如果没有的话那么就不会生成任何文件
            isGenerate=false;//判断是否要生成文件,初值为true
        }
    }

修改循环代码

        if(!isGenerate){//如果没有需要的注解就不生成
            return isGenerate;false
        }
        processOnAPIService(roundEnv);

        List<FieldSpec> fieldSpecs =new ArrayList<>();
        for (int i = 0; i <mServiceElements.size() ; i++) {
            FieldSpec fieldSpec =FieldSpec
//这里只是想看一下加的注解有没有被识别,如果输出数量正确那么就可以继续
//当然如果你想要看element里面有什么其他的东西那么也可以用类似的方法
                    .builder(String.class,"apiService"+mServiceElements.size())
                    .addModifiers(Modifier.PRIVATE,Modifier.STATIC)
                    .build();
            fieldSpecs.add(fieldSpec);
        }

生成的类

//我四个类上加了注解
  private static String apiService4;

  private static String apiService4;

  private static String apiService4;

  private static String apiService4;

好了要开始装逼了...

for (int i = 0; i <mServiceElements.size() ; i++) {
            //获取一下element后面要用到,element就是被注解的类,里面所有元素都可以获取到,不懂就看源码很清晰
            Element element =mServiceElements.get(i);
            //类型名称同理
            TypeName typeName=TypeName.get(element.asType());
            FieldSpec fieldSpec =FieldSpec
                    //要改动的就是这个方法的两个参数
                    .builder(typeName,element.getSimpleName().toString())
                    .addModifiers(Modifier.PRIVATE,Modifier.STATIC)
                    .build();
            fieldSpecs.add(fieldSpec);
        }

生成的类

//已经很像了,只是首字母应该要小写
  private static ApiService ApiService;

  private static BreadtripAPI BreadtripAPI;

  private static QyerGuideAPI QyerGuideAPI;

  private static TravelNotesAPI TravelNotesAPI;

我没有找到直接从element中获取小写的方式,只能用方法改变一下首字母了

public static String firstUpperToLetter(String str){
        char[] array = str.toCharArray();
        array[0] += 32;
        return String.valueOf(array);
    }
//明显在后面输出方法的时候会用到,用变量接一下
            String fieldName=firstUpperToLetter(element.getSimpleName().toString());
            FieldSpec fieldSpec =FieldSpec
                    .builder(typeName,fieldName)

make 一下 ,生成的文件就不贴了,自己看吧

第三步

接下来就要生成方法了,其实和生成变量差不多的

MethodSpec method = MethodSpec.methodBuilder("get"+element.getSimpleName().toString())//生成方法名
                    .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
                    .returns(typeName)
                    //先硬写一下生成看看,一会慢慢改
                    .addStatement("return apiService==null ?\n" +
                            "                HttpServiceManager.getInstance().getRetrofit().create(ApiService.class)\n" +
                            "                :apiService;")
                    .build();
            methodSpecs.add(method);

TypeSpec helloWorld = TypeSpec.classBuilder(CLASS_NAME)
                .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                .addFields(fieldSpecs)
                .addMethods(methodSpecs)//添加多个方法
                .build();

生成的类

//除了方法的内容基本上差不多了
  public static ApiService getApiService() {
    return apiService==null ?
                        HttpServiceManager.getInstance().getRetrofit().create(ApiService.class)
                        :apiService;;
  }

  public static BreadtripAPI getBreadtripAPI() {
    return apiService==null ?
                        HttpServiceManager.getInstance().getRetrofit().create(ApiService.class)
                        :apiService;;
  }

修改方法内容

这里最好看一下javapoet中 $ 的用法

MethodSpec method = MethodSpec.methodBuilder("get"+element.getSimpleName().toString())
                    .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
                    .returns(typeName)
//这里主要是$T的使用,如果是使用fieldName来拼接字符传的形式,那么不会自动生成import的,所以这里要使用$T.class的方式
//那么为什么不使用$S标识fieldName呢,可以自己试一下。
                    .addStatement("return "+fieldName+"==null ?\n" +
                            "                HttpServiceManager.getInstance().getRetrofit().create($T.class)\n" +
                            "                :"+fieldName+";",typeName)//此处拼接一个;号纯属为了让类出错,如果去掉你的类就不报错啦
                    .build();

生成的类

  public static ApiService getApiService() {
    return apiService==null ?
                        HttpServiceManager.getInstance().getRetrofit().create(ApiService.class)
                        :apiService;;//去掉;号这个类就ok了
  }

  public static BreadtripAPI getBreadtripAPI() {
    return breadtripAPI==null ?
                        HttpServiceManager.getInstance().getRetrofit().create(BreadtripAPI.class)
                        :breadtripAPI;;
  }

当然细心的同学会发现

HttpServiceManager.getInstance().getRetrofit().create

这一句还没有自动生成,那么你就自己来做吧,用上面所说的方式应该很简单的了,别忘记还有个annotation APIManager

小结

到这里使用APT生成文件的一些基础操作就结束了,后面要生成更复杂一些的文件,不过万变不离其宗,使用的思想都是差不多的,这里先留个坑,后面研究好了用一两章来填上。

上面的代码还是很简陋的,具体优化的方式可以根据自己经验去做。

如果大家发现了什么问题,或者某个地方有更好的方式实现也请告诉我,感谢~

github:hackerlc

最后再推荐一篇文章
android-apt 即将退出历史舞台

章节目录

第一章 APT之生成Retrofit+Rxjava管理类
第二章 APT之HelloWorld Class
第三章 APT之生成接口管理类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容