基于RxJava Retrofit的网络框架(一)

基于RxJava Retrofit网络框架的搭建

RxJava、Retrofit两个第三方库的优势

RxJava的使用场景

本文讨论的如无特殊说明,均指代rxjava2 和 retrofit2。
我们先讨论一下rxJava引入的背景。有一些应用场景,特别是一些复杂的异步回调场景,如果使用传统的开发模式,会如何实现。
列举一些场景:

  1. EditText: 根据输入内容自动搜索时,只有当输入间隔大于某个duration时,才触发搜索。
    同时要根据输入内容,实时判断其有效性。
    对输入的内容,实时做出改变(比如不能输入数字,特殊字符等)
    https://www.jianshu.com/p/9aaccd7bb600
  2. 联合判断
    https://www.jianshu.com/p/88ce90240396
  3. 注册--登录--获取用户信息(注册的一般流程) 获取权限--获取经纬度--鉴权--生成订单(生成订单一般流程) 这样一连串的网络请求。
  4. 结合多个接口的数据,再更新UI;或者历史记录、购物车记录等,需要合并本地缓存和网络请求返回的数据;
  5. 网络请求前置条件token的获取,token可以本地缓存,本地缓存拿不到要去网络获取。同时对于token拿不到时的网络请求先等待,token获取后将等待的请求一一发出。甚至token请求需要设置重试次数,超过次数才停止请求
  6. 防重复操作,倒计时
  7. 多重缓存处理:先读取缓存更新UI,在网络请求更新UI(https://www.jianshu.com/p/7474950af2df
  8. 多处场景需要同一个监听事件,但是每个场景注册时间点不同,如何做到事件产生之后注册的监听者也能收到回调。

这些应用场景几乎是每个app都会遇到的,传统的处理方式:层层递进(回调),各种标志位控制,嵌套的if else,各种异步调用分散在代码各处,要将各个回调结果汇总,并且综合处理回调的不同状态。而且由于异步回调的特点,代码处理方式不会像同步调用那样直接可控,异步回调导致的时序问题又将问题复杂度提高了几个level。我经常会想,这种callback的异步调用方式,为何会将代码变得丑陋不堪和难以维护?设计模式中只提供了观察者模式,但是对于观察者回调复杂后,没有提出更好的解决方案。RxJava(或者说响应式编程)主要解决了这类的问题。
关于RxJava是如何解决以上场景中遇到的问题,以及如何和retrofit一起联合使用,短短几行无法说不清,在基于RxJava Retrofit的网络框架(二)中会详解。

Retrofit的使用

Retrofit并不实现网络请求本身(网络请求由okhttp负责),他是一个框架,为网络请求本身提供可扩展,可配置,易使用的外部封装。okhttp负责提高网络请求的性能和兼容性,Retrofit负责更好的使用他。框架的重要性不言而喻,好的框架让使用者关注更少细节,轻易的扩展。okhttp好像汽车的发动机,对于汽车性能和稳定性起到决定性作用,框架就是除了发动机以外其他部分,除了对发送机功能的整合外,漂亮的外观,舒适易用的体验才是人们愿意开这台车的原因。
以下列举Retrofit相对于volley Android-Async-Http等框架的优势

  1. 简洁易用:通过注解配置网络参数,大量设计模式(建造者模式,工厂)简化配置和使用。
  2. 功能强大:支持RxJava方式(也支持callback方式),支持同步&异步,
  3. 耦合度低,扩展性好:模块高度封装,彻底解耦。

由于Android-Async-Http的停更,Google对volley基本放弃的态度,这两套框架使用者和价值日渐减少。反观okhttp,Google官方应用则广泛使用。作为okhttp的黄金搭档(同为square公司出品),Retrofit可以说是目前Android app网络请求框架的不二选择,与okhttp搭配,是性能、稳定性、兼容性、易用性都达到很高水平的框架组合。

Observable网络框架的抽象

Observable网络框架建立的原因

  1. Retrofit已经对网络请求做了封装,为什么还要封装?
    网络请求中对于请求流程、配置、加解密、异常处理对于每个app都是固定不变的,如果业务每次请求都自己处理逻辑,会存在冗余代码,且质量不易保证。所以我们需要基于Retrofit对请求流程、配置、加解密、异常处理等操作做二次封装,并对调用方式进行统一。
  2. 框架封装方式Observable是什么?
    对网络请求二次封装(一般为异步请求),传统使用callback方式异步回调网络请求结果。但是这种callback的方式,没有利用到Retrofit的一大优势--rxjava调用,所以我们要基于rxjava调用方式,封装一个基于Observable的网络请求框架。
    以下所说网络框架,均指基于Observable的网络请求二次封装框架。

Observable网络框架要解决的问题

网络框架要帮助业务处理以下几个问题:

  1. 支持Get Post请求,对Request的参数业务可轻松配置
  2. 对Request 参数做发送前处理:组合和加密处理
  3. 返回Response 解密处理,Java实体化
  4. 返回Response code码判断及处理
  5. 网络请求cancle机制 progressBar配置等通用处理
    达到的目标:业务使用框架时,只需要关注业务相关(Request参数,Response返回值的配置和处理),其他都交给框架处理。同时对于网络请求的属性可配置(error是否提示,progressBar是否显示等)

Observable网络框架如何实现

设计原则:

  1. 网络请求Api返回Observable对象,作为网络请求事件的生产者:
    生产者负责请求的发起,和返回的所有预处理。
  2. 为业务提供BaseObserver类,使用者实现其子类作为消费者
    消费者基类提供对response的一般处理,消费者业务也可以使用自己Observer处理

在Observable的创建过程中,框架如何封装?

首先我们需要一个Manager或Helper全局实例,通过他可以发起网络请求,一般设计为单例全局持有,有利于网络请求一些资源的共用。
我们暂定为NetHelper,其网络请求接口定义为:

private static Observable<R> sendRequest(final HttpRequest request, final TypeReference<R> t)

sendRequest方法中,我们来看下Observable对象的生成过程:此处我们基于Retrofit本身Observable生成方式,我们先看下Retrofit最基础是如何创建Observable的。

第一步,定义Request,Request类的定义在Retrofit里通过注解的方式完成的

public interface Request {

    @POST("{url}")
    Observable<JSONObject> postJSONResult(@Path(value="url",encoded =true) String url, @FieldMap Map<String, String> params);
}

可以看到我们的定义方式是通用的(每个request都可以复用),每个request都是通过postJSONResult方法获取observable,传入自己的url和params即可完成不同的网络请求。

第二步,创建retrofit
NetHelper.java中

// 初始化okhttp
OkHttpClient client = new OkHttpClient.Builder()
        .build();

// 初始化Retrofit
retrofit = new Retrofit.Builder()
        .client(client)
        .baseUrl(Request.HOST)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(MyConverterFactory.create())
        .build();

OkHttpClient每次请求的时候都要创建,注意:OkHttpClient.Builder()中有ConnectionPool作为OkHttp的连接池要复用,否则请求过多时容易导致内存溢出。
创建Retrofit实例过程中,设置了okHttpClient,baseUrl,调用方式rxJava(通过addCallAdapterFactory)
GsonConverterFactory这些都是一般的写法,GsonConverterFactory作用是把Response通过GSon转为javaBean。App业务中一般是先解密后Gson转,所以此处使用MyConverterFactory实现解密功能。

第三步,生成Observable
这一步是生成observable的过程,与httpRequest本身有关(我们前面提到了Request类是一个支持Retrofit通用类,业务自定义的请求类HttpRequest实现了 getURLParam() getURLAction()等方法),所以这个获取Observable的方法可以放到HttpRequest中进行(NetHelper.sendRequest方法是传入了HttpRequest对象的)

Request request = retrofit.create(Request.class);
return request.postJSONResult(getURLAction(),getURLParam());

对于httpRequest中入参的组合和加密,实现在getURLParam()方法里。
备注:我们的网络post请求params是query形式的,如果是body表单,还需要另外的处理方式。

以上三步,已经初步将Observable返回。通过以上几步只是基于Retrofit自身的Observable创建方法做了一些封装。下面的处理是框架的重点和核心:

private static Observable<R> sendRequest(final HttpRequest request,final TypeReference<R> t){

        return NetHelper.getApiObservable(request)
                .compose(ResponseTransformer.handleResult())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());

    }

注意:sendRequest方法一定要Observable所有的链式操作执行完后在返回。


NetHelper.getApiObservable方法后,再加上网络请求的线程配置,这时候业务subscribe消费者,就可以直接得到解密后的JsonObject了。注意此时是string而不是Retrofit通常的JavaBean,这是因为我们要定义一个通用的Request类,将其接口返回值定义为了Observable<JSONObject>,所以我们还需要一步转换。

    .map(new Function<JSONObject,R>() {
        @Override
        public R apply(JSONObject jsonObject) throws Exception {
            if (jsonObject != null){
                R response = jsonObject.toJavaObject(con);
                if (orgRequest != null) {
                    HttpHelper.printHttpLog(orgRequest, jsonObject.toString());
                }
                return response;
            } else {
                return null;
            }
        }
    })

response的异常处理,progressbar的显示等,也需要架构统一处理。我们引入了ResponseTransformer,你可以把他理解为map操作符,在交给消费者前对response结果做了处理。

public static <T extends Serializable> ObservableTransformer<T, T> handleResult() {
    return upstream -> upstream
            .onErrorResumeNext(new ErrorResumeFunction<T>())
            .flatMap(new ResponseFunction<T>());
}

private static class ErrorResumeFunction<T extends Serializable> implements Function<Throwable, ObservableSource<? extends T>> {

    @Override
    public ObservableSource<? extends T> apply(Throwable throwable) throws Exception {
        return Observable.error(CustomException.handleException(throwable));
    }
}

private static class ResponseFunction<T extends Serializable> implements Function<T, ObservableSource<T>> {

    @Override
    public ObservableSource<T> apply(T tResponse) throws Exception {
        int code = tResponse.getCode();
        String message = tResponse.getMsg();

        if (code == SUCCESS.value()) {
            return Observable.just(tResponse);
        } else {
            return Observable.error(new ApiException(code, message));
        }
    }
}

可以看出对于事件流upstream,做了正常和异常的再分流,对于服务器错误(超时,404等)通过onErrorResumeFunction继续抛出Observable.error,
对于正常返回,根据response中code的定义,只有SUCCESS时才返回数据Observable.just(),其他情况(业务错误等)作为错误情况继续抛出。
你可能有两个疑问,一个是response中code的判定可以在observer中处理吗,另一个是服务器错误和业务错误为何都作为error抛出。

第一个问题:
code值的判定不可以在observer中处理,而必须在Observable一端处理。因为Observable形式的网络请求是作为数据流中的一环出现的,可能当前网络请求只是一连串异步调用(rxjava调用)的一环。
第二个问题:
response中code!=SUCCESS是业务错误的情况,必须向数据流中发出,让业务处理此异常。(那同时对于Response的定义也是,code!=SUCCESS必须是不需要业务处理的情况才行)
两种错误都抛出error(内部code不同),方便架构使用者在事件响应时,既能捕捉所有错误,又能区分错误的类型。


哪些处理放到了BaseObserver中?

BaseObserver顾名思义,是架构使用者在rxjava流式调用最后一步所使用的观察者基类,他适合将网络请求的UI响应放入其中。

public  abstract class   BaseObserver<T > implements Observer<T>{
  /**
    * 请求成功
    * @param t
    */
   public abstract void onSuccess(T t);

   /**
    * 请求失败
    * @param
    * @param object
    */
   public abstract void onFail(ApiException);

   @Override
    public void onSubscribe(Disposable d) {
        if (isShowProgress()) {
            showProgress(true);
        }
    }

    @Override
    public void onNext(T t) {
        if (isShowProgress()) {
            showProgress(false);
        }
        onSuccess(t);
    }

    @Override
    public void onError(Throwable e) {
      if (isShowProgress()) {
          showProgress(false);
      }

      if (e instanceof ApiException){
            ApiException apiException = (ApiException)e;
            switch(apiException.getCode()){
              case:NOT_LOGIN
                break;
              case:TOKEN_ERROR
                break;
            }
      }
    }

    /**
     * 网络请求是否loading显示
     * @return
     */
    protected boolean isShowProgress(){
        return true;
    }
}

BaseObserver中,我们可以看到处理了showProgress,ApiException做了处理。同时可以对progressBar是否显示做配置。可以满足一般的网络请求架构使用,当然也可以自行subscribe自己的Observer。

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

推荐阅读更多精彩内容