《一个Android工程的从零开始》-8、base(七) Retrofit的封装

先扯两句

隔了这么长时间,深深的负罪感终于督促我写下了这篇博客,当然,之前的时间也不全是在玩,参加了一个面试,进行了我人生中的第一次霸面,对方有个要求就是完成他们的demo,就可以得到面试机会,结果我完成以后单纯的就去了,再然后就没有然后了。
不过至少在这次demo中,我的基本框架得到了应用,还是让自己很欣慰的,另外还使用了一些博客后续将要与大家分享的内容,算是一次提前的实战吧,效果自认为还算满意。
如果关注我博客的大家应该知道,我前段时间写的就是Retrofit的内容,所以今天就从Retrofit的封装开始写起吧。
闲言少叙,老规矩还是先上我的Git库,然后开始正文。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
另外,也把我做的这个小demo也发到的了库中,也没太深的东西内容,大家如果感兴趣的话也而已去看看:
https://github.com/BanShouWeng/IYuBaTestApplication
Retrofit的Header封装部分放在了《一个Android工程的从零开始》阶段总结与修改1-base,如有需要的朋友可以去其中查看。

正文

关于Retrofit的相关内容呢,如果大家有什么不太清楚的地方,可以看一下我的上一篇博客Android开发相关——Retrofit+RxJava+OkHttp(下)使用,或许有人会疑问,为什么给了下,没给上,主要还是因为下才是使用,上具体是什么,好奇的也可以去看一下Android开发相关——Retrofit+RxJava+OkHttp(上)闲扯,虽然我估计有一部分人,看到“闲扯”二字,或许就没兴趣了,不过刚步入android世界中的大家还是可以去看看的,或许会有收获也说不定。
下面就正是开始封装的部分:

分析

其是封装,说白了就是为了我们在运用的时候能够更方便,从我个人的角度出发还是两个字——偷懒!
从《Android开发相关——Retrofit+RxJava+OkHttp(下)使用》中,大家或许也知道了,Retrofit网络访问框架需要的东西:

  1. 访问数据回调接口
  2. 访问方法

所以想要封装,我们需要做的事,自然就是从这两大块入手,看看其中有哪些是可以直接复用的内容,而这些内容就是我们可以拿来偷懒的点:

访问数据回调接口

 public interface Movie2Service {
        @GET("top250") 
        Observable<ResponseBody> getTop250(@Query("start") int start, @Query("count")int count);
    }

从上面的接口中,我们可以看得出来,其中我们可以操作的点,有三个:

  1. 尾址
  2. 参数
  3. 返回数据model(也就是Bean对象)

当然,如果你一定要说接口名也算的话,我也不反对。至少,在我一会说的“封装方法一”中是不会反对的,具体为什么,我这里先卖个关子,一会再聊。而这三个可操作的点呢,在“封装方法二”中才会用到,所以依然卖个关子,我们一会再聊,大家只需要暂时知道这三个点一会可以操作就行。

访问方法

    private void getMovie2() {
        String baseUrl = "https://api.douban.com/v2/movie/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        Movie2Service movieService = retrofit.create(Movie2Service.class);

        movieService.getTopMovie(10, 10)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            String responseString = responseBody.string();
                            Log.i("responseString", responseString);
                            LogUtil.info("response", responseString);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

这个访问方法中,可以修改的点,同样显而易见:

  1. baseUrl
  2. 结果回调,也就是subscribe传入的接口

至于其他的部分,比如解析方式是json还是xml啊,是否使用RxJava啊,又或者线程的方式之类的参数自然也可以动态设置,只是对于一个工程项目而言,除非十分必要的情况下,考虑到开发难度以及开发周期等等诸多因素,很少会故意难为开发人员,而是采用一种万能的模式即可,所以这里我就将这些因素都忽略掉了,如果你真的遇到一个这么变态的产品,我只能在这里为你默默祈福了。
而这两部分中,baseUrl也算是比较特殊的存在,一般情况下,比较小的项目中基本只适用一个baseUrl就可以结束战斗,哪怕大的项目,最多也就是每个模块一个baseUrl,如果再大的,暂时还没接触到,但是想来也不会多多少。毕竟涉及到域名、端口等等问题,当然,在我看来这些都不是原因,主要还是后台的战友们,也懒啊!
好吧,以上都是玩笑话,不过baseUrl很少有在网络封装的方法中体现的,无脑一点的方法就是封装Utils包下的Const类中的静态常量中,而相对正式点的玩法则是封装到app mudule的build.grade中,并且可以去BuildConfig文件中查找,具体的玩法说来也简单,不过谁让我懒,还是直接上代码以及BuildConfig目录位置。

build.grade配置信息

signingConfigs {
        debug {
            storeFile file("./bsw.keystore")
            storePassword "bswbsw"
            keyAlias "wsbsw"
            keyPassword "wsbswhhh"
        }

        release {
            storeFile file("./bsw.keystore")
            storePassword "bswbsw"
            keyAlias "wsbsw"
            keyPassword "wsbswhhh"
        }
    }

    buildTypes {
        release {
            debug{
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
                signingConfig signingConfigs.debug
            }
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
            signingConfig signingConfigs.release
        }
    }

BuildConfig位置

这里写图片描述

BuildConfig内信息

package com.banshouweng.mybaseapplication;

public final class BuildConfig {
      public static final boolean DEBUG = Boolean.parseBoolean("true");
      public static final String APPLICATION_ID = "com.banshouweng.mybaseapplication";
      public static final String BUILD_TYPE = "debug";
      public static final String FLAVOR = "";
      public static final int VERSION_CODE = 1;
      public static final String VERSION_NAME = "1.0";
  // Fields from build type: debug
      public static final String BASE_URL_NAME = "baseUrl地址";
}

// Fields from build type: debug下的内容是我们自行添加的信息

build.grade配置信息
其中minifyEnabled 与proguardFiles 是在创建项目的时候自带的,分别代表是否混淆,而proguardFiles 太专业了,我也说不明白,大家找自己看一下吧

proguardFiles这部分有两段,前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,免去了我们很多事,这个文件的目录在 /tools/proguard/proguard-android.txt , 后一部分是我们项目里的自定义的混淆文件,目录就在 app/proguard-rules.txt , 如果你用Studio 1.0创建的新项目默认生成的文件名是 proguard-rules.pro , 这个名字没关系,在这个文件里你可以声明一些第三方依赖的一些混淆规则。

而其他部分则是我们都需要添加的了,buildConfigField中就是我们要添加的baseUrl,格式已经给出,但是需要切记的一点是,这里添加的都是字符串,例如:

//输入
  buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
  buildConfigField "int", "COUNT", "0"
  buildConfigField "boolean", "ISOK", "true"

//显示
// Fields from build type: debug
  public static final String BASE_URL_NAME = "baseUrl地址";
  public static final boolean ISOK = true;
  public static final int COUNT = 0;

//引用
  String url = DebugConfig.BASE_URL_NAME;
  int count = DebugConfig.COUNT;
  boolean isOk= DebugConfig.ISOK;

可以看得出来,如果我们赋值的内容都需要传递的是字符串,显示的才是我们需要的内容,或者说AS用的是更无脑的玩法,那就是去掉最外层的引号,所以当创建String类型的参数时,需要使用的是""baseUrl地址""的形式(加粗斜体的所有部分),,如果只加一层引号的话,就会出现如下效果:

这里写图片描述

当然,如果我们将int或boolean的引号去掉,又会是另一个效果:

这里写图片描述

ssigningConfig signingConfigs.release这行就是以上的内容,将会在何时触发加载到BuildConfig文件中,我这里是分两种情况:1、debug(自行调试的时候);2、release(发行的时候)。
而ssigningConfig 中则是对应两种情况的签名信息:

  1. storeFile file("./bsw.keystore"):签名文件位置(../xxx.keystore(或者xxx.jks))
  2. storePassword "bswbsw"签名文件密码
  3. keyAlias "wsbsw" 签名别名
  4. keyPassword "wsbswhhh" 别名密码

这些全都配置好,我们点击Sync Now的时候,编译结束,在BuildConfig中才会有我们要的内容,如果只添加了release而不添加debug,那么就只有在发行包中才会在BuildConfig文件下生成对应静态常量,而我们平时开所处的环境是debug状态,所以这个时候,是我们是无法在BuildConfig中看到对应的静态常量的,所以开发时也找不到,调试时也用也会报错,所以开发时一定要对应添加debug和release才可以

这里写图片描述

关于build.gradle,文件自然还有许多其他的妙用,这里就先不列举了,我们还是回归到正文,也就是说,baseUrl通过这上述的Const或者DebugConfig这两种方法集成即可,就不需要额外花时间了。
所以重头戏也就都在结果回调的接口上了。

封装方法1——最无脑的封装

看了这哥标题一定会有人问,什么叫最无脑的封装,很好理解啊,那就是封装起来特别简单,用起来,相当麻烦。又有人会问,既然麻烦为什么还要去这么封装,其实很简单,这种封装的用途就是为了应付那些脑洞打开的产品的,万一他们真想出来什么丧心病狂的需求,我们还无法不去完成的情况下,自然就需要用这种麻烦的封装了。
至于为什么不直接使用Retrofit,一定还要封装一下,自然是万一产品下次又爆发了一个相类似的脑洞的情况下,我们可以稍做修改,便能直接拿来用,一旦直接使用Retrofit了,下次还得重新写,或者再去找之前加到哪里了,麻烦!
而这种封装所需要的包(对包的部分不太清楚的参见《一个Android工程的从零开始》-1前期准备)便是apimagager以及service。
service自不必说,看名字也能知道,它肯定是存放回调接口的,而回调接口我们自然也能加一定的处理,从Retrofit官网中,我们可以发现其中有两种玩法很有趣,可以拿来用一下,我们先来看一下效果:

public interface Movie2Service {
    @GET("{action}")
    Observable<ResponseBody> getTopMovie(@Path("action") String action, @QueryMap Map<String, String> params);
}

显而易见,就是@Path以及@QueryMap,先说@QueryMap,其实对比一下之前的@Query就会发现,它只不过是在后面加了一个Map,至于功能,还是传递的参数,只不过原本的玩法需要传递一个名字为name的String参数John,那就要先定义一个String变量,命名name,然后赋值。这样原本来说也不麻烦,可是一旦让传递大量参数的时候,就显得有些不便了,所以就使用了Map去传递,同样的传递John,只需要一个map.put("name", "john");即可,大量数据的时候,继续put就好,什么时候都put好了,将这个map传递进来就达到了目的。
接下来就是@Path,先看看官方的说法:

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }. A corresponding parameter must be annotated with @Path using the same string.

嗯,除了看不懂没有别的缺点了!所以我也不打算逐字去翻译了,其实看例子也能看出来,这就是一个占位符,用“{占位符名}”站好位,然后@Path("占位符名")按照其中的占位符名,将传递进来的内容内容放置在对应的占位符名所占的位置上。所以传进来的action就会被当做我们的尾址来使用。而我们的返回Bean则依照要求取就好,我这里是使用的OKHttp的ResponseBody。如此,Service就完成了。

apimagager,这部分就完整看产品的需求了,基本框架在上面,自行添加修改一下就好,当然返回值是肯定需要处理一下的,不过这部分我会在下一个封装方法中详细说明,这里只管调用即可。

封装方法2

这次没给额外的说明,也是我最后的一种封装方法,就是打算弄一个一招鲜吃遍天的玩法,虽然封装起来会麻烦许多,但是有点也很明显,用着方便,至于方便什么,必然是方便偷懒了。
先说说我给这个封装方法找的位置,作为一个懒人,虽然这个方法也可以放置在ApiMageger中,但是调用的时候,竟然还得让我new个对象,有这时间我给自己new个女朋友好不。或者说是用静态方法,不过说实话,静态的方法或者常量变量还是尽量减少使用,毕竟静态内存也不富裕,再说不考虑这个,我们不还是需要ApiMageger.getXXX吗。有这时间,我研究要追那个妹子好不。
总之,在偷懒心理作祟下,我选择了将这个网络访问的封装放置在了BaseActivity以及BaseFragment中,用的时候直接掉方法即可。
不过如果遇到个别的需要在封装的Adapter中掉访问网络的方法时就比较尴尬,需要多费些周折,例如发个EvenBus之类的,说起来也不算麻烦。
具体是封装在APIManager中还是封装在BaseActivity/BaseFragment就看你个人的需求情况,我下面的内容就先按照封装在BaseActivity/BaseFragment进行,相信提出来放在APIManager的操作,对大家来说还是小菜一碟的。

数据回调1


    //网络访问
    public <T extends BaseBean> void get1(final String action, final Class<T> cls) {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        GetService1 getService = retrofit.create(GetService1.class);
        if (params == null) {
            params = new HashMap<>();
        }
        getService.get(action, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            T t = new Gson().fromJson(responseBody.string(), cls);
                            success(action, t);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    //成功方法
    public void success(String action, BaseBean baseBean) {

    }
    //失败方法
    public void error(String action, Throwable e) {

    }

在上述方法之前,先创建和一个共有的Map类进行传参,由于是继承的我们的BaseActivity/BaseFragment,所以这里不需要我们传递Map参数了,直接调用即可。因为这里我们要使用一个万能的封装法,很遗憾的是,在使用Retrofit的时候,直接使用泛型T传递的时候,会报错Observer< T >不能直接使用,所以我暂时的解决方法只能是传递OkHttp中的ResponseBody类,然后通过string()方法获取其中的json串,再通过GSON解析成我们需要的对应类。并将其传递到success方法中,在访问的Activity重写该方法即可,至于为什么要将action也传递回来,主要是在进行多次网络请求的时候,用来分辨对应区分是谁发出的请求,随后做出对应的处理。
不过这个方法的缺点就是:1、我们需要额外重写成功或者失败的方法,还是有点麻烦;2、success方法中的参数是BaseBean(参见附录1),而不是我们想要生成的Bean对象,还需要强转一下。
当然以上属于吹毛求疵,不过为了偷懒,所以我就想到了下面的方法2。

封装方法2

这次没给额外的说明,也是我最后的一种封装方法,就是打算弄一个一招鲜吃遍天的玩法,虽然封装起来会麻烦许多,但是有点也很明显,用着方便,至于方便什么,必然是方便偷懒了。
先说说我给这个封装方法找的位置,作为一个懒人,虽然这个方法也可以放置在ApiMageger中,但是调用的时候,竟然还得让我new个对象,有这时间我给自己new个女朋友好不。或者说是用静态方法,不过说实话,静态的方法或者常量变量还是尽量减少使用,毕竟静态内存也不富裕,再说不考虑这个,我们不还是需要ApiMageger.getXXX吗。有这时间,我研究要追那个妹子好不。
总之,在偷懒心理作祟下,我选择了将这个网络访问的封装放置在了BaseNetActivity以及BaseNetFragment中,用的时候直接掉方法即可。
不过如果遇到个别的需要在封装的Adapter中掉访问网络的方法时就比较尴尬,需要多费些周折,例如发个EvenBus之类的,说起来也不算麻烦。
具体是封装在APIManager中还是封装在BaseNetActivity/BaseNetFragment就看你个人的需求情况,我下面的内容就先按照封装在BaseNetActivity/BaseNetFragment进行,相信提出来放在APIManager的操作,对大家来说还是小菜一碟的。

对于封装,第一件事就是需要我们创建一个BaseNetActivity/BaseNetFragment,至于为什么没有按照本篇博客修改之前放到BaseActivity/BaseFragment中,主要是有一些界面还是不需要网络访问的,虽然少一些,但是集成这些东西还是很好资源的,另外就是之前我们的BaseActivity中已经放了很多内容,如果网络访问的内容也放到其中难免有写太冗杂了,所以网络访问的部分就单独拿出来了一个BaseNetActivity/BaseNetFragment。当然,与之前的Activity/Fragment不同的是,BaseNetActivity/BaseNetFragment不需要我们关联布局文件,因为它的工作只是网络访问而已,BaseNetActivity/BaseNetFragment继承BaseActivity/BaseFragment这样就可以使用到网络与布局的双封装了。
看了前面的封装方法一,很显然,它的作用就是对应每一个创建对应Service,既然如此,大家一定猜到了,这部分的封装就是一个通用的方法了,也就是无论你想要用什么样子的Bean都随你心情。
先上Service代码:

public interface RetrofitGetService {
    @GET("{action}")
    Observable<ResponseBody> getResult(@Path("action") String action, @QueryMap Map<String, String> params);
}

大家可以看得出来,这个部分与我们上面的方法一举例用的是完全相同的,而为什么值用ResponseBody,主要还是为了其提供的string()方法可以获得JSON串,方便我们自行转换。
再就是创建初始化retrofit的方法:

private void initBaseData() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(5, TimeUnit.SECONDS);
        retrofit = new Retrofit.Builder()
                .client(builder.build())
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

其中OkHttpClient的作用就是connectTimeout方法,用于设置5s链接超时的处理。

随后就是结果回调的接口,用于将得到的结果传回去 ,这部分其实用单纯的方法也可以,在Activity中重写就可以得到结果参数,可是懒人我实在懒得去记需要重写的方法名,用接口的就可以很好的回避掉这点了。

public interface ResultCallBack<T extends BaseBean> {
        void success(String action, T t);

        void error(String action, Throwable e);
 }

万事俱备,下面就该进行正式的封装部分了

    /**
     * Get请求
     *
     * @param action   请求接口的尾址,如“top250”
     * @param clazz    要转换的Bean类型(需继承BaseBean)
     * @param callBack 结果回调接口
     * @param <T>      用于继承BaseBean的占位变量
     */
    public <T extends BaseBean> void get(final String action, final Class<T> clazz, final ResultCallBack callBack) {

        if (getService == null) {
            getService = retrofit.create(RetrofitGetService.class);
        }
        if (params == null) {
            params = new HashMap<>();
        }
        getService.getResult(action, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    /**
     * Post请求
     *
     * @param action   请求接口的尾址,如“top250”
     * @param clazz    要转换的Bean类型(需继承BaseBean)
     * @param callBack 结果回调接口
     * @param <T>      用于继承BaseBean的占位变量
     */
    public <T extends BaseBean> void post(final String action, final Class<T> clazz, final ResultCallBack callBack) {
        if (postService == null) {
            postService = retrofit.create(RetrofitPostService.class);
        }

        if (params == null) {
            params = new HashMap<>();
        }
        postService.postResult(action, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

可以看到,JSON转换成Bean使用的是Gson解析,其余没有变化,如果有一些各个接口都需要添加的参数,就可以对应的方法中直接添加,以免重复操作。

疑问##

虽然我的封装达到了目的,可是Retrofit本身不应该出现前面出现的问题,我这里也有一个疑问,究竟是我还没有找到Retrofit的正确使用方式,还是Retrofit自身却是存在这个漏洞,也希望大家有所发现能为我指点迷津,在此先谢过各位了。BaseNetActivity完整代码参见附录2

附录

附录1

BaseBean:
其实也没什么特殊的部分,其中都是一些基础的部分,比如网络访问是否成功之类的处理,具体大家可以参考各种errorCode,就比如打击熟悉的404就是其中的一种,当然,这个错误码肯定与404不同,而是后台定义的错误码,常用的场合就是登录时,用户没有注册、账户密码不正确之类的错误情况判断。
再有一点就是,将BaseBean序列号,便于Activity与Activity之间,或者Activity与Fragment之间传值。

public class BaseBean implements Serializable {}

这里实现序列号的方法是实现Serializable 接口,当然还有一种玩法就是实现Parcelable接口,至于两种的区别,请参见Serializable 和 Parcelable 两种序列化

附录2

public class BaseNetActivity extends BaseActivity {
    private String baseUrl = "https://api.douban.com/v2/movie/";
    private RetrofitGetService getService;
    private RetrofitPostService postService;
    private Retrofit retrofit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initBaseData();
    }

    private void initBaseData() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(5, TimeUnit.SECONDS);
        retrofit = new Retrofit.Builder()
                .client(builder.build())
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

    /**
     * Get请求
     *
     * @param action   请求接口的尾址,如“top250”
     * @param clazz    要转换的Bean类型(需继承BaseBean)
     * @param callBack 结果回调接口
     * @param <T>      用于继承BaseBean的占位变量
     */
    public <T extends BaseBean> void get(final String action, final Class<T> clazz, final ResultCallBack callBack) {

        if (getService == null) {
            getService = retrofit.create(RetrofitGetService.class);
        }
        if (params == null) {
            params = new HashMap<>();
        }
        params.put("start", "0");
        params.put("count", "10");

        getService.getResult(action, null, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            String responseString = responseBody.string();
                            Log.i("responseString", "responseString get  " + responseString);
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    /**
     * Post请求
     *
     * @param action   请求接口的尾址,如“top250”
     * @param clazz    要转换的Bean类型(需继承BaseBean)
     * @param callBack 结果回调接口
     * @param <T>      用于继承BaseBean的占位变量
     */
    public <T extends BaseBean> void post(final String action, final Class<T> clazz, final ResultCallBack callBack) {
        if (postService == null) {
            postService = retrofit.create(RetrofitPostService.class);
        }

        if (params == null) {
            params = new HashMap<>();
        }
        params.put("start", "0");
        params.put("count", "10");

        postService.postResult(action, null, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull ResponseBody responseBody) {
                        try {
                            String responseString = responseBody.string();
                            Log.i("responseString", "responseString post  " + responseString);
                            callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        callBack.error(action, e);
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    public interface ResultCallBack<T extends BaseBean> {
        void success(String action, T t);

        void error(String action, Throwable e);
    }
}

附录3

《一个Android工程的从零开始》- 目录

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

推荐阅读更多精彩内容