基于RxJava Retrofit的網絡框架(一)

基于RxJava Retrofit網絡框架的搭建

RxJava、Retrofit兩個第三方庫的優勢

RxJava的使用場景

本文討論的如無特殊說明,均指代rxjava2 和 retrofit2。
我們先討論一下rxJava引入的背景。有一些應用場景,特別是一些復雜的異步回調場景,如果使用傳統的開發模式,會如何實現。
列舉一些場景:

  1. EditText: 根據輸入內容自動搜索時,只有當輸入間隔大于某個duration時,才觸發搜索。
    同時要根據輸入內容,實時判斷其有效性。
    對輸入的內容,實時做出改變(比如不能輸入數字,特殊字符等)
    http://www.lxweimin.com/p/9aaccd7bb600
  2. 聯合判斷
    http://www.lxweimin.com/p/88ce90240396
  3. 注冊--登錄--獲取用戶信息(注冊的一般流程) 獲取權限--獲取經緯度--鑒權--生成訂單(生成訂單一般流程) 這樣一連串的網絡請求。
  4. 結合多個接口的數據,再更新UI;或者歷史記錄、購物車記錄等,需要合并本地緩存和網絡請求返回的數據;
  5. 網絡請求前置條件token的獲取,token可以本地緩存,本地緩存拿不到要去網絡獲取。同時對于token拿不到時的網絡請求先等待,token獲取后將等待的請求一一發出。甚至token請求需要設置重試次數,超過次數才停止請求
  6. 防重復操作,倒計時
  7. 多重緩存處理:先讀取緩存更新UI,在網絡請求更新UI(http://www.lxweimin.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閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容