基于RxJava2+Retrofit+RxCache的網絡請求封裝

這套框架來源于現有項目,這幾天開了新項目正好用到順手就把這套框架抽出來了,也方便以后使用。目前網上對Rxjava2+Retrofit2的封裝真是太多了,但是大體思路都是一樣的,而且好多代碼都具有相似性,這套其實也不例外,大家可選擇性使用。
  首先我們先不說封裝思路,先說說這套框架都都具有哪些功能及如何使用;

功能

1 使用RxCache緩存機制,可自定義緩存過期時間,及數據分頁緩存等功能。
2 統一的請求錯誤處理;
3 統一的網絡狀態判斷處理;
4 基于HttpLoggingInterceptor的請求日志打印。

以上就是這套框架可以實現的功能,框架中并沒有像其他的一樣封裝了ProgressBar
,因為每個項目不同,ProgressBar的樣式需求并不一樣,就算同一個項目中下拉刷新和普通加載可能也不一樣,所以需要使用的小伙伴自己定義ProgressBar。
  此外這套框架使用了RxCache實現緩存,而并不是通過OKHttp緩存,所以這套框架對服務器沒有任何要求,不需要定義好Header之類的東西。如果你對RxCache不熟悉,可以看看這篇文章,或者RxCache的官網。不知道這么牛的框架為啥start目前才1000多呢,不說廢話了,來看使用方式。

使用方式

調用以下代碼完成網絡請求

//聲明監聽
HttpSubscriber mHttpObserver =new HttpSubscriber(new OnResultCallBack<TestBean>() {
        @Override
        public void onSuccess(TestBean tb) {
        }
        @Override
        public void onError(int code,String errorMsg) {
        }
    });
//發起請求    
HttpManager.getInstance().getDatas(mHttpObserver,1,10,"json",true);

//取消請求
 mHttpObserver.unSubscribe();

看起來是不是很簡單?

下面來說說上面的代碼是如何完成網絡數據請求的。先來看看這個框架的結構

僅僅只有七個類而已。

簡單介紹一下個各類的職責
ApiResponse——封裝的返回數據模板(里面的error_code,reason,result名稱需要你跟后臺的小伙伴對應好,通常情況下error_code代碼狀態碼,reason為成功或失敗的提示信息,result中為具體的數據,由于數據格式未知所以使用泛型代表)
ApiService——Retrofit的數據請求接口。注意一下每個方法的返回值類型。(我們真正需要的是TestBean中的數據它必須被ApiResponse包裝,最后返回Observable類型)
CacheProvider——RxCache的緩存接口,注意它的第一個參數類型必須和Retrofit數據請求接口的返回值類型一樣。
OnResultCallBack——請求成功或失敗的回調。
ApiException——公共的請求錯誤處理類。
HttpSubscriber——公共的請求訂閱者。
HttpManager——發起請求的管理類。

首先是引入的各種庫文件

//Rxjava2
compile 'io.reactivex.rxjava2:rxjava:2.0.7'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
//Retrofit2
compile 'com.squareup.retrofit2:retrofit:latest.release'
compile 'com.squareup.retrofit2:converter-gson:latest.release'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
//RxCache
compile "com.github.VictorAlbertos.RxCache:runtime:1.8.0-2.x"
compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.3'
//Okhttp-interceptor
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'

HttpManager的初始化

public static HttpManager getInstance() {
    if (instance == null) {
        synchronized (HttpManager.class) {
            if (instance == null) {
                instance = new HttpManager();
            }
        }
    }
    return instance;
}

單例模式的HttpManager,來看HttpManager的構造函數

private HttpManager() {
    HttpLoggingInterceptor.Level level= HttpLoggingInterceptor.Level.BODY;
    HttpLoggingInterceptor loggingInterceptor=new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
        @Override
        public void log(String message) {
            Log.i("HttpManager",message);
        }
    });
    loggingInterceptor.setLevel(level);
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .addInterceptor(loggingInterceptor);
    OkHttpClient okHttpClient = builder.build();

    mRetrofit = new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl(Constant.BASE_URL)
            .client(okHttpClient)
            .build();

    cacheProvider = new RxCache.Builder()
            .persistence(mContext.getFilesDir(), new GsonSpeaker())
            .using(CacheProvider.class);

    mApiService = mRetrofit.create(ApiService.class);
}

在構造函數中,首先我們通過HttpLoggingInterceptor設置了攔截器,并通過addInterceptor方法設置給 OkHttpClient.Builder。然后是構建OkHttpClient.Builder及okHttpClient ,再然后是Retrofit的初始化。
下面接著是RxCache的初始化,并在其中設置了緩存目錄,mContext為ApplicationContext,由init方法傳入。

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        HttpManager.init(this);//不做任何操作僅僅是緩存一下Application引用
    }
}

那么HttpManager是如何完成一個網絡請求的呢?

1 ApiService中聲明請求接口

@FormUrlEncoded
@POST("query?key=7c2d1da3b8634a2b9fe8848c3a9edcba")
Observable<ApiResponse<TestBean>> getDatas(@Field("pno") int pno, @Field("ps") int ps, @Field("dtype") String dtype);

2CacheProvider中聲明緩存接口(如果需要緩存就寫)

@LifeCache(duration = 5, timeUnit = TimeUnit.MINUTES)
Observable<ApiResponse<TestBean>> getDatas(Observable<ApiResponse<TestBean>> oRepos, EvictProvider evictDynamicKey);

注意看注解,可以自定義緩存過期時間。(EvictProvider 參數是設置是否需要對請求的數據進行緩存,具體可以看這里)同時,第一個參數一定要是Observable<ApiResponse<TestBean>> 我們在ApiService中聲明好的getDatas方法的返回值。
3 HttpManager聲明對應的請求方法
方法有兩種,帶緩存的方式

  public void getDatasWithCache(Observer<TestBean> subscriber, int pno, int ps, String dtype, boolean update) {
    toSubscribe(cacheProvider.getDatas(mApiService.getDatas(pno, ps,dtype),new EvictProvider(update)), subscriber);
}

不帶緩存的方式

public void getDatasNoCache(Observer<TestBean> subscriber, int pno, int ps, String dtype) {
    toSubscribe(mApiService.getDatas(pno, ps,dtype), subscriber);
}

看到沒不帶緩存的方法只是沒有用cacheProvider.getDatas()方法包裹,同時少了一個控制是否更新的參數update。
再來看一下toSubscribe()方法的實現

private <T> void toSubscribe(Observable<ApiResponse<T>> o, Observer<T> s) {
    o.subscribeOn(Schedulers.io())
            .map(new Function<ApiResponse<T>, T>() {
                @Override
                public T apply(@NonNull ApiResponse<T> response) throws Exception {
                    int code=Integer.parseInt(response.getCode());
                    if (code!=Constant.SUCCESS_CODE) {
                        throw new ApiException(code, response.getMsg());
                    } else {
                        if (response.getDatas() == null) {
                            return (T) response.getMsg();
                        } else {
                            return response.getDatas();
                        }
                    }
                }
            })
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(s);
}

借助Rxjava的map方法完成數據的進一步轉換,注意SUCCESS_CODE,這個值是數據返回成功時的狀態碼,需要你和后臺小伙伴定義好,一般情況下都為0。如果code!=SUCCESS_CODE,那么出錯返回的code的和message都會拋給ApiException,在ApiException中的getApiExceptionMessage方法你可以根據具體code重新定義不同的message,或者使用傳入進來的message,通常情況下這個類不需要修改,如果需要客戶端根據code自定義message那么就按照上面所的方式修改即可。最后所有的message都會拋給HttpSubscriber的onError方法

    @Override
public void onError(Throwable e) {
    if (e instanceof CompositeException) {
        CompositeException compositeE=(CompositeException)e;
        for (Throwable throwable : compositeE.getExceptions()) {
            if (throwable instanceof SocketTimeoutException) {
                mOnResultListener.onError(ApiException.Code_TimeOut,ApiException.SOCKET_TIMEOUT_EXCEPTION);
            } else if (throwable instanceof ConnectException) {
                mOnResultListener.onError(ApiException.Code_UnConnected,ApiException.CONNECT_EXCEPTION);
            } else if (throwable instanceof UnknownHostException) {
                mOnResultListener.onError(ApiException.Code_UnConnected,ApiException.CONNECT_EXCEPTION);
            } else if (throwable instanceof RxCacheException) {
                //緩存異常暫時不做處理
            }  else if (throwable instanceof MalformedJsonException) {
                mOnResultListener.onError(ApiException.Code_MalformedJson,ApiException.MALFORMED_JSON_EXCEPTION);
            }
        }
    }else {
        mOnResultListener.onError(ApiException.Code_Other,e.getMessage());
    }
}

在這個方法中也統一處理了網絡問題。注意看不同的網絡狀態返回的狀態碼是不一樣的。

SocketTimeoutException 網絡超時 1000
ConnectException 鏈接異常 1001
UnknownHostException Host異常 1001
MalformedJsonException 解析異常 1020
其他錯誤信息統一返回 1003(因為code在此基本沒什么用了,重要的是錯誤提示信息)
最終執行到

mHttpObserver =new HttpSubscriber(new OnResultCallBack<TestBean>() {
        @Override
        public void onSuccess(TestBean tb) {
        }
        @Override
        public void onError(int code,String errorMsg) {
        }
    });

中的onError方法,在這里我們根據不同的code展示不同的界面即可(例如常見的網絡錯誤界面),或者通過Toast等其他方式給用戶提示。
再回到toSubscribe方法,如果數據返回成功了,即code==SUCCESS_CODE,那么數據會返回給HttpSubscriber的onNext方法

@Override
public void onNext(T t) {
    if (mOnResultListener != null) {
        mOnResultListener.onSuccess(t);
    }
}

onNext中調用OnResultCallBack的onSuccess方法,把數據傳遞到

mHttpObserver =new HttpSubscriber(new OnResultCallBack<TestBean>() {
        @Override
        public void onSuccess(TestBean tb) {
        }
        @Override
        public void onError(int code,String errorMsg) {
        }
    });

中的onSuccess中,在這里我們把數據展示給用戶即可。
HttpSubscriber的另一項任務就是取消數據請求操作。
在onSubscribe中通過一個全局的mDisposable記錄當前的Disposable

 @Override
public void onSubscribe(Disposable d) {
    mDisposable = d;
}

在需要取消的地方調用unSubscribe()即可

public void unSubscribe() {
    if (mDisposable != null && !mDisposable.isDisposed()) {
        mDisposable.dispose();
    }
}

下面運行一下程序

   mHttpObserver =new HttpSubscriber(new OnResultCallBack<TestBean>() {
        @Override
        public void onSuccess(TestBean tb) {
            for (TestBean.ListBean bean : tb.getList()) {
                result+=bean.toString();
            }
            resultTv.setText(result);
        }
        @Override
        public void onError(int code,String errorMsg) {
            resultTv.setText("onError: code:"+code+"  errorMsg:"+errorMsg );
        }
    });

這里成功后通過TextView顯示一下數據,失敗時也顯示一下錯誤信息和code;
首先是不使用緩存

 HttpManager.getInstance().getDatasNoCache(mHttpObserver,1,10,"json");

數據請求成功,再來測試斷網情況


斷網后返回了錯誤碼1003。
下面測試緩存

設置一個5分鐘的緩存

@LifeCache(duration = 5, timeUnit = TimeUnit.MINUTES)
Observable<ApiResponse<TestBean>> getDatas(Observable<ApiResponse<TestBean>> oRepos, EvictProvider evictDynamicKey);

 HttpManager.getInstance().getDatasWithCache(mHttpObserver,1,10,"json",false);

可以看到第一次請求產生了網絡流量,說明數據來自網絡,而后的幾次請求均沒有產生流量,說明數據來自本地緩存。
好了這套框架就介紹到這里吧,有需要的可以在這里下載
https://github.com/shiweibsw/EasyHttp

如何使用?

1 build.gradle中導入插件
2 將http包下所有內容拷貝到你的工程即可。

下一步的計劃

1 retrofit 接口類封裝基本的get和post請求;
2 支持以插件的形式導入工程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容