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 的,趕快用上它來改造你想網絡請求庫吧!!! **

參考博客:
RxJava 與 Retrofit 結合的最佳實踐

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

推薦閱讀更多精彩內容