Retrofit + RxJava + OkHttp 讓網(wǎng)絡請求變的簡單-封裝篇

前面一篇文章講了一下Retrofit+ RxJava 請求網(wǎng)絡的一些基本用法,還沒有看過的可以去看一下Retrofit + RxJava + OkHttp 讓網(wǎng)絡請求變的簡單-基礎篇,正如標題所說的,Retrofit+RxJava 是讓我們的網(wǎng)絡請求變得簡單,代碼精簡量瓜。通過前一篇文章,我們感覺寫一個請求還是有點麻煩,作為程序員滓侍,我們的目標就是“偷懶”,絕不重復搬磚牲芋。因此我們還需要封裝一下撩笆,來簡化我們使用捺球,接下來進入正題。

一夕冲,創(chuàng)建一個統(tǒng)一生成接口實例的管理類RetrofitServiceManager

我們知道氮兵,每一個請求,都需要一個接口歹鱼,里面定義了請求方法和請求參數(shù)等等泣栈,而獲取接口實例需要通過一個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(){  
      // 創(chuàng)建 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);//讀操作超時時間  
      // 添加公共參數(shù)攔截器        
HttpCommonInterceptor commonInterceptor = new HttpCommonInterceptor.Builder() 
               .addHeaderParams("paltform","android") 
               .addHeaderParams("userToken","1234343434dfdfd3434") 
               .addHeaderParams("userId","123445")      
               .build();        
builder.addInterceptor(commonInterceptor);    
    // 創(chuàng)建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);    
}
}

說明:創(chuàng)建了一個RetrofitServiceManager類,該類采用單例模式庭敦,在私有的構(gòu)造方法中疼进,生成了Retrofit 實例,并配置了OkHttpClient和一些公共配置秧廉。提供了一個create()方法伞广,生成接口實例,接收Class范型疼电,因此項目中所有的接口實例Service都可以用這個來生成嚼锄,代碼如下:

mMovieService = RetrofitServiceManager.getInstance().create(MovieService.class);

通過create()方法生成了一個MovieService

二,創(chuàng)建接口澜沟,通過第一步獲取實例

上面已經(jīng)有了可以獲取接口實例的方法因此我們需要創(chuàng)建一個接口灾票,代碼如下:

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

三茫虽,創(chuàng)建一個業(yè)務Loader 刊苍,如XXXLoder,獲取Observable并處理相關(guān)業(yè)務

解釋一下為什么會出現(xiàn)Loader ,我看其他相關(guān)文章說濒析,每一個Api 都寫一個接口正什,我覺得這樣很麻煩,因此就把請求邏輯封裝在在一個業(yè)務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);   
 }
}

創(chuàng)建一個MovieLoader,構(gòu)造方法中生成了mMovieService,而Service 中可以定義和業(yè)務相關(guān)的多個api,比如:例子中的MovieService中,
可以定義和電影相關(guān)的多個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 中的調(diào)用

創(chuàng)建Loader實例

mMovieLoader = new MovieLoader();

通過Loader 調(diào)用方法獲取結(jié)果,代碼如下:

/*
 *
 * 獲取電影列表 
 */
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());     
      }  
  });
}

以上就完成請求過程的封裝透绩,現(xiàn)在添加一個新的請求翘骂,只需要添加一個業(yè)務Loader 類,然后通過Loader調(diào)用方法獲取結(jié)果就行了渺贤,是不是方便了很多雏胃?但是在實際項目中這樣是不夠的,還能做進一步簡化志鞍。

五,統(tǒng)一處理結(jié)果和錯誤

1,統(tǒng)一處理請求結(jié)果

現(xiàn)實項目中方仿,所有接口的返回結(jié)果都是同一格式固棚,如:

 {
 "status": 200,
 "message": "成功",
 "data": {}
}

我們在請求api 接口的時候,只關(guān)心我們想要的數(shù)據(jù)仙蚜,也就上面的data,其他的東西我們不太關(guān)心此洲,請求失敗的時候可以根據(jù)status判斷進行錯誤處理,所以我們需要包裝一下委粉。首先需要根據(jù)服務端定義的JSON 結(jié)構(gòu)創(chuàng)建一個BaseResponse 類呜师,代碼如下:

/*
*
* 
* 網(wǎng)絡請求結(jié)果 基類 
* 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;  
  }
}

有了統(tǒng)一的格式數(shù)據(jù)后,我們需要剝離出data 返回給上層調(diào)用者贾节,創(chuàng)建一個PayLoad 類汁汗,代碼如下:

/*
* 
*
* 剝離 最終數(shù)據(jù) 
* Created by zhouwei on 16/11/10. 
*/
public class PayLoad<T> implements Func1<BaseResponse<T>,T>{    
@Override 
   public T call(BaseResponse<T> tBaseResponse) {//獲取數(shù)據(jù)失敗時,包裝一個Fault 拋給上層處理錯誤
        if(!tBaseResponse.isSuccess()){ 
           throw new Fault(tBaseResponse.status,tBaseResponse.message);  
      }    
    return tBaseResponse.data;  
  }
}

PayLoad 繼承自Func1,接收一個BaseResponse<T> , 就是接口返回的JSON數(shù)據(jù)結(jié)構(gòu)栗涂,返回的是T,就是data,判斷是否請求成功知牌,請求成功返回Data,請求失敗包裝成一個Fault 返回給上層統(tǒng)一處理錯誤。在Loader類里面獲取結(jié)果后斤程,通過map 操作符剝離數(shù)據(jù)角寸。代碼如下:

public Observable<List<Movie>> getMovie(int start, int count){ 
 return observe(mMovieService.getTop250(start,count))        
  .map(new PayLoad<BaseResponse<List<Movie>>>());
}

2,統(tǒng)一處理錯誤

在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){      
      //錯誤處理   
     }  
  }
}

以上就可以對應錯誤碼處理相應的錯誤了亮曹。

六橄杨,添加公共參數(shù)

在實際項目中秘症,每個接口都有一些基本的相同的參數(shù),我們稱之為公共參數(shù)式矫,比如:userId乡摹、userToken、userName,deviceId等等采转,我們不必要聪廉,每個接口都去寫,這樣就太麻煩了故慈,因此我們可以寫一個攔截器板熊,在攔截器里面攔截請求,為每個請求都添加相同的公共參數(shù)察绷。攔截器代碼如下:

/*
 *
 * 攔截器
 *
 * 向請求頭里添加公共參數(shù) 
 * 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();    
    // 添加新的參數(shù)干签,添加到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()); 

       //添加公共參數(shù),添加到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;     
   } 
   }
}

以上就是添加公共參數(shù)的攔截器,在RetrofitServiceManager 類里面加入OkHttpClient 配置就好了拆撼。代碼如下:

// 添加公共參數(shù)攔截器
HttpCommonInterceptor commonInterceptor = new HttpCommonInterceptor.Builder()     
       .addHeaderParams("paltform","android")   
       .addHeaderParams("userToken","1234343434dfdfd3434") 
       .addHeaderParams("userId","123445")      
       .build();
builder.addInterceptor(commonInterceptor);

這樣每個請求都添加了公共參數(shù)容劳。

** 好了,以上一個簡易的網(wǎng)絡請求庫就封裝得差不多了闸度,完整代碼請戳Retrofit + RxJava +OkHttp 簡易封裝基本上能滿足項目中的網(wǎng)絡請求竭贩,由于項目中暫時沒有文件上傳下載的需求,這一塊還沒有添加莺禁,后面有時間會補充這一塊的東西留量。**

封裝的類放在http包下:


包結(jié)構(gòu).png

最后放幾張Demo示例的效果圖:(數(shù)據(jù)來自干貨集中營)
重點是看妹紙!S炊Bハā(滑稽臉)

福利列表.png

電影列表:(數(shù)據(jù)來自豆瓣)

電影列表.png

**以上就是封裝的全部內(nèi)容,還沒有用Retrofit 的柒傻,趕快用上它來改造你想網(wǎng)絡請求庫吧P⒑铡!红符! **

參考博客:
RxJava 與 Retrofit 結(jié)合的最佳實踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末青柄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子预侯,更是在濱河造成了極大的恐慌致开,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萎馅,死亡現(xiàn)場離奇詭異双戳,居然都是意外死亡,警方通過查閱死者的電腦和手機糜芳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門飒货,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魄衅,“玉大人,你說我怎么就攤上這事塘辅』纬妫” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵扣墩,是天一觀的道長哲银。 經(jīng)常有香客問我,道長呻惕,這世上最難降的妖魔是什么荆责? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮亚脆,結(jié)果婚禮上做院,老公的妹妹穿的比我還像新娘。我一直安慰自己濒持,他們只是感情好山憨,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弥喉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玛迄。 梳的紋絲不亂的頭發(fā)上由境,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音蓖议,去河邊找鬼虏杰。 笑死,一個胖子當著我的面吹牛勒虾,可吹牛的內(nèi)容都是我干的纺阔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼修然,長吁一口氣:“原來是場噩夢啊……” “哼笛钝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起愕宋,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤玻靡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后中贝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囤捻,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年邻寿,在試婚紗的時候發(fā)現(xiàn)自己被綠了蝎土。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片视哑。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖誊涯,靈堂內(nèi)的尸體忽然破棺而出挡毅,到底是詐尸還是另有隱情,我是刑警寧澤醋拧,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布慷嗜,位于F島的核電站,受9級特大地震影響丹壕,放射性物質(zhì)發(fā)生泄漏庆械。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一菌赖、第九天 我趴在偏房一處隱蔽的房頂上張望缭乘。 院中可真熱鬧,春花似錦琉用、人聲如沸堕绩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奴紧。三九已至,卻和暖如春晶丘,著一層夾襖步出監(jiān)牢的瞬間黍氮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工浅浮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沫浆,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓滚秩,卻偏偏與公主長得像专执,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子郁油,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容