Retrofit + RxJava + OkHttp 讓網路請求變的簡單-封裝篇
前面一篇文章講了一下Retrofit+ RxJava 請求網路的一些基本用法,還沒有看過的可以去看一下Retrofit + RxJava + OkHttp 讓網路請求變的簡單-基礎篇,正如標題所說的,Retrofit+RxJava 是讓我們的網路請求變得簡單,程式碼精簡。通過前一篇文章,我們感覺寫一個請求還是有點麻煩,作為程式設計師,我們的目標就是“偷懶”,絕不重複搬磚。因此我們還需要封裝一下,來簡化我們使用,接下來進入正題。
一,建立一個統一生成介面例項的管理類RetrofitServiceManager
我們知道,每一個請求,都需要一個介面,裡面定義了請求方法和請求引數等等,而獲取介面例項需要通過一個Retrofit例項,這一步都是相同的,因此,我們可以把這些相同的部分抽取出來,程式碼如下:
/*
*
* Created by zhouwei on 16/11/9.
*/
public class RetrofitServiceManager {
private static final int DEFAULT_TIME_OUT = 5;//超時時間 5s
private static final int DEFAULT_READ_TIME_OUT = 10;
private Retrofit mRetrofit;
private RetrofitServiceManager(){
// 建立 OKHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS);//連線超時時間 builder.writeTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS);//寫操作 超時時間
builder.readTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS);//讀操作超時時間
// 新增公共引數攔截器
HttpCommonInterceptor commonInterceptor = new HttpCommonInterceptor.Builder()
.addHeaderParams("paltform","android")
.addHeaderParams("userToken","1234343434dfdfd3434")
.addHeaderParams("userId","123445")
.build();
builder.addInterceptor(commonInterceptor);
// 建立Retrofit
mRetrofit = new Retrofit.Builder()
.client(builder.build())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(ApiConfig.BASE_URL)
.build();
}
private static class SingletonHolder{
private static final RetrofitServiceManager INSTANCE = new RetrofitServiceManager();
}
/**
* 獲取RetrofitServiceManager
* @return
*/
public static RetrofitServiceManager getInstance(){
return SingletonHolder.INSTANCE;
}
/**
* 獲取對應的Service
* @param service Service 的 class
* @param <T>
* @return
*/
public <T> T create(Class<T> service){
return mRetrofit.create(service);
}
}
說明:建立了一個RetrofitServiceManager類,該類採用單例模式,在私有的構造方法中,生成了Retrofit 例項,並配置了OkHttpClient和一些公共配置。提供了一個create()方法,生成介面例項,接收Class範型,因此專案中所有的介面例項Service都可以用這個來生成,程式碼如下:
mMovieService = RetrofitServiceManager.getInstance().create(MovieService.class);
通過create()方法生成了一個MovieService
二,建立介面,通過第一步獲取例項
上面已經有了可以獲取介面例項的方法因此我們需要建立一個介面,程式碼如下:
public interface MovieService{
//獲取豆瓣Top250 榜單
@GET("top250")
Observable<MovieSubject> getTop250(@Query("start") int start, @Query("count")int count);
@FormUrlEncoded
@POST("/x3/weather")
Call<String> getWeather(@Field("cityId") String cityId, @Field("key") String key);
}
好了,有了介面我們就可以獲取到介面例項了mMovieService
三,建立一個業務Loader ,如XXXLoder,獲取Observable並處理相關業務
解釋一下為什麼會出現Loader ,我看其他相關文章說,每一個Api 都寫一個介面,我覺得這樣很麻煩,因此就把請求邏輯封裝在在一個業務Loader 裡面,一個Loader裡面可以處理多個Api 介面。程式碼如下:
/*
*
* Created by zhouwei on 16/11/10.
*/
public class MovieLoader extends ObjectLoader {
private MovieService mMovieService;
public MovieLoader(){
mMovieService = RetrofitServiceManager.getInstance().create(MovieService.class);
}
/**
* 獲取電影列表
* @param start
* @param count
* @return
*/
public Observable<List<Movie>> getMovie(int start, int count){
return observe(mMovieService.getTop250(start,count))
.map(new Func1<MovieSubject, List<Movie>>() {
@Override
public List<Movie> call(MovieSubject movieSubject) {
return movieSubject.subjects;
}
});
}
public Observable<String> getWeatherList(String cityId,String key){
return observe(mMovieService.getWeather(cityId,key))
.map(new Func1<String, String>() {
@Override
public String call(String s) {
//可以處理對應的邏輯後在返回
return s;
}
});
}
public interface MovieService{
//獲取豆瓣Top250 榜單
@GET("top250")
Observable<MovieSubject> getTop250(@Query("start") int start, @Query("count")int count);
@FormUrlEncoded
@POST("/x3/weather")
Call<String> getWeather(@Field("cityId") String cityId, @Field("key") String key);
}
}
建立一個MovieLoader,構造方法中生成了mMovieService,而Service 中可以定義和業務相關的多個api,比如:例子中的MovieService中,
可以定義和電影相關的多個api,獲取電影列表、獲取電影詳情、搜尋電影等api,就不用定義多個介面了。
上面的程式碼中,MovieLoader是從ObjectLoader 中繼承下來的,ObjectLoader 提取了一些公共的操作。程式碼如下:
/**
*
* 將一些重複的操作提出來,放到父類以免Loader 裡每個介面都有重複程式碼
* Created by zhouwei on 16/11/10.
*
*/
public class ObjectLoader {
/**
*
* @param observable
* @param <T>
* @return
*/
protected <T> Observable<T> observe(Observable<T> observable){
return observable
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
相當於一個公共方法,其實也可以放在一個工具類裡面,後面做快取的時候會用到這個父類,所以就把這個方法放到父類裡面。
四,Activity/Fragment 中的呼叫
建立Loader例項
mMovieLoader = new MovieLoader();
通過Loader 呼叫方法獲取結果,程式碼如下:
/*
*
* 獲取電影列表
*/
private void getMovieList(){
mMovieLoader.getMovie(0,10).subscribe(new Action1<List<Movie>>() {
@Override
public void call(List<Movie> movies) {
mMovieAdapter.setMovies(movies);
mMovieAdapter.notifyDataSetChanged();
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.e("TAG","error message:"+throwable.getMessage());
}
});
}
以上就完成請求過程的封裝,現在新增一個新的請求,只需要新增一個業務Loader 類,然後通過Loader呼叫方法獲取結果就行了,是不是方便了很多?但是在實際專案中這樣是不夠的,還能做進一步簡化。
五,統一處理結果和錯誤
1,統一處理請求結果
現實專案中,所有介面的返回結果都是同一格式,如:
{
"status": 200,
"message": "成功",
"data": {}
}
我們在請求api 介面的時候,只關心我們想要的資料,也就上面的data,其他的東西我們不太關心,請求失敗的時候可以根據status判斷進行錯誤處理,所以我們需要包裝一下。首先需要根據服務端定義的JSON 結構建立一個BaseResponse 類,程式碼如下:
/*
*
*
* 網路請求結果 基類
* Created by zhouwei on 16/11/10.
*/
public class BaseResponse<T> {
public int status;
public String message;
public T data;
public boolean isSuccess(){
return status == 200;
}
}
有了統一的格式資料後,我們需要剝離出data 返回給上層呼叫者,建立一個PayLoad 類,程式碼如下:
/*
*
*
* 剝離 最終資料
* Created by zhouwei on 16/11/10.
*/
public class PayLoad<T> implements Func1<BaseResponse<T>,T>{
@Override
public T call(BaseResponse<T> tBaseResponse) {//獲取資料失敗時,包裝一個Fault 拋給上層處理錯誤
if(!tBaseResponse.isSuccess()){
throw new Fault(tBaseResponse.status,tBaseResponse.message);
}
return tBaseResponse.data;
}
}
PayLoad 繼承自Func1,接收一個BaseResponse<T> , 就是介面返回的JSON資料結構,返回的是T,就是data,判斷是否請求成功,請求成功返回Data,請求失敗包裝成一個Fault 返回給上層統一處理錯誤。在Loader類裡面獲取結果後,通過map 操作符剝離資料。程式碼如下:
public Observable<List<Movie>> getMovie(int start, int count){
return observe(mMovieService.getTop250(start,count))
.map(new PayLoad<BaseResponse<List<Movie>>>());
}
2,統一處理錯誤
在PayLoad 類裡面,請求失敗時,丟擲了一個Fault 異常給上層,我在Activity/Fragment 中拿到這個異常,然後判斷錯誤碼,進行異常處理。在onError () 中新增程式碼如下:
public void call(Throwable throwable) {
Log.e("TAG","error message:"+throwable.getMessage());
if(throwable instanceof Fault){
Fault fault = (Fault) throwable;
if(fault.getErrorCode() == 404){
//錯誤處理
}else if(fault.getErrorCode() == 500){
//錯誤處理
}else if(fault.getErrorCode() == 501){
//錯誤處理
}
}
}
以上就可以對應錯誤碼處理相應的錯誤了。
六,新增公共引數
在實際專案中,每個介面都有一些基本的相同的引數,我們稱之為公共引數,比如:userId、userToken、userName,deviceId等等,我們不必要,每個介面都去寫,這樣就太麻煩了,因此我們可以寫一個攔截器,在攔截器裡面攔截請求,為每個請求都新增相同的公共引數。攔截器程式碼如下:
/*
*
* 攔截器
*
* 向請求頭裡新增公共引數
* Created by zhouwei on 16/11/10.
*/
public class HttpCommonInterceptor implements Interceptor {
private Map<String,String> mHeaderParamsMap = new HashMap<>();
public HttpCommonInterceptor() {
}
@Override
public Response intercept(Chain chain) throws IOException {
Log.d("HttpCommonInterceptor","add common params");
Request oldRequest = chain.request();
// 新增新的引數,新增到url 中
/* HttpUrl.Builder authorizedUrlBuilder = oldRequest.url() .newBuilder()
.scheme(oldRequest.url().scheme())
.host(oldRequest.url().host());*/
// 新的請求
Request.Builder requestBuilder = oldRequest.newBuilder();
requestBuilder.method(oldRequest.method(),
oldRequest.body());
//新增公共引數,新增到header中
if(mHeaderParamsMap.size() > 0){
for(Map.Entry<String,String> params:mHeaderParamsMap.entrySet()){
requestBuilder.header(params.getKey(),params.getValue());
}
}
Request newRequest = requestBuilder.build();
return chain.proceed(newRequest);
}
public static class Builder{
HttpCommonInterceptor mHttpCommonInterceptor;
public Builder(){
mHttpCommonInterceptor = new HttpCommonInterceptor();
}
public Builder addHeaderParams(String key, String value){
mHttpCommonInterceptor.mHeaderParamsMap.put(key,value);
return this;
}
public Builder addHeaderParams(String key, int value){
return addHeaderParams(key, String.valueOf(value));
}
public Builder addHeaderParams(String key, float value){
return addHeaderParams(key, String.valueOf(value));
}
public Builder addHeaderParams(String key, long value){
return addHeaderParams(key, String.valueOf(value));
}
public Builder addHeaderParams(String key, double value){
return addHeaderParams(key, String.valueOf(value));
}
public HttpCommonInterceptor build(){
return mHttpCommonInterceptor;
}
}
}
以上就是新增公共引數的攔截器,在RetrofitServiceManager 類裡面加入OkHttpClient 配置就好了。程式碼如下:
// 新增公共引數攔截器
HttpCommonInterceptor commonInterceptor = new HttpCommonInterceptor.Builder()
.addHeaderParams("paltform","android")
.addHeaderParams("userToken","1234343434dfdfd3434")
.addHeaderParams("userId","123445")
.build();
builder.addInterceptor(commonInterceptor);
這樣每個請求都添加了公共引數。
好了,以上一個簡易的網路請求庫就封裝得差不多了,完整程式碼請戳Retrofit + RxJava +OkHttp 簡易封裝基本上能滿足專案中的網路請求,由於專案中暫時沒有檔案上傳下載的需求,這一塊還沒有新增,後面有時間會補充這一塊的東西。
封裝的類放在http包下:
包結構.png
最後放幾張Demo示例的效果圖:(資料來自乾貨集中營)
重點是看妹紙!!!(滑稽臉)
福利列表.png
電影列表:(資料來自豆瓣)
電影列表.png
以上就是封裝的全部內容,還沒有用Retrofit 的,趕快用上它來改造你想網路請求庫吧!!!