Retrofit + RxJava + OkHttp 讓網路請求變的簡單-封裝篇

yangxi_001發表於2017-05-17

前面一篇文章講了一下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 結合的最佳實踐

相關文章