《一个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吗。有这时间,我研究要追那个妹子好不。
总之,在偷懒心理作祟下,我选择了将这个网络访问的封装放置在了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解析,其余没有变化,如果有一些各个接口都需要添加的参数,就可以对应的方法中直接添加,以免重复操作。BaseNetActivity完整代码参见附录2。

疑问

虽然我的封装达到了目的,可是retrofit.create方法导致的这个问题实在是让我很纠结,虽然在接口中定义了泛型,可是public T create(final Class service),无法传入定义的泛型,所以暂时只能想到这种迂回的方法,如果大家发现什么好的方法解决这个问题的话,也欢迎分享。

附录

附录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工程的从零开始》- 目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值