Android RxJava系列三: 與Retrofit2結合使用和封

joytoy發表於2021-09-09

前言

本篇文章主要介紹Rxjava與Retrofit結合使用,對Rxjava和Retrofit不熟悉的可以去看我之前的兩篇介紹

基本使用

定義請求介面

public interface GetRequest_Interface {

    @POST("/app/auth/school/list")
    Observable<School> postSchool(@Body RequestBody route);//根據學校名獲取學校

}
   

建立 Retrofit介面例項


GetRequest_Interface request = new Retrofit.Builder()
                .baseUrl(API.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build().create(GetRequest_Interface.class);

構建請求引數

這裡以請求體為Json 字串為準

HashMap<String, Object> map = new HashMap<>();
        map.put(key, value);
        Gson gson=new Gson();
        String strEntity = gson.toJson(map);
        KLog.d("22222222RequestBody"+strEntity);
        RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json;charset=UTF-8"),strEntity);

開始網路請求

 Observable<School> observable = request.postSchool(body);

 observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<School>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                       //此處做一些請求開始時的初始化事件,例如彈出一個dialog
                    }

                    @Override
                    public void onNext(School school) {
                        //此處處理請求成功業務(code == 200 )
                    }

                    @Override
                    public void onError(Throwable e) {
                       //此處處理請求失敗業務(code != 200 )
                    }

                    @Override
                    public void onComplete() {
                     //請求完成處理業務,關閉請求佇列,關閉dialog等
                    }
                });

至此,Rxjava 與 Retrofit 結合基本使用就結束了,基於以上就可以愉快的完成網路請求工作了,是不是很方便簡潔.

當然了,對於我們的業務來說,不可能只有一次網路請求,基本處處都需要進去網路請求,而且也不可能如上面一樣,如此簡單. 一般我們的業務中都需要配置一些其他的引數資訊,如果每一次網路請求都像上面那樣寫的話,當然是可以的,但是你的程式碼就太low了,也不符合程式設計規範.

所以基於你會熟練的使用了的前提下,我們就需要將以上程式碼進行簡單封裝.

關於封裝我想多說一句

對於封裝,很多人都認為封裝就是使程式碼足夠簡潔,邏輯足夠清晰,符合開閉原則等,的確是這樣的,但是需要使情況而定,如果你寫的程式碼是服務廣大人群,也就是開源專案,那就要考慮很多因素,做到足夠開放.

但如果只是用到自己的專案裡,那我們必須要明確一點,那就是定製化的前提是符合自己業務的需求,而不要過於封裝.所以也就有為什麼我們常常需要對別人的開源框架做二次封裝,就是這個道理,沒有最好的封裝,只有最合適的封裝.

封裝篇

Url統一存放

public interface API {

   //此處存放所有的BaseUrl
    String BASE_URL = ""; //核心後臺API
    String BASE_SCHOOL_URL = ""; //學校API


}

存放所有請求api

public interface GetRequest_Interface {

    /*-------------------------------------所有網路請求 API-------------------------------------------------------*/


    @POST("/app/auth/captcha")
    Observable<Phone> postPhone(@Body RequestBody route);  //獲取驗證碼
    @POST("/app/auth/login")
    Observable<RegistLogin> postRegist(@Body RequestBody route);//登入註冊

}

封裝處理網路請求所需的引數資訊

  • 初始化 配置 OkHttpClient
根據自己業務需求初始化OkHttpClient

 OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)  //l 連線超時時間
                .writeTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS)  //讀寫超時
                .readTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS)   //讀取超時
                .retryOnConnectionFailure(true)  //失敗重連
                .addNetworkInterceptor(tokenInterceptor)  //新增網路攔截器
                .addInterceptor(tokenRespInterceptor)
                //.authenticator(authenticator)  //授權認證
                .build();

這裡需要用到OkHttp3的攔截器相關內容,不熟悉的可以先去了解

  • 統一新增公共請求頭
 Interceptor tokenInterceptor = new Interceptor() {  //全域性攔截器,
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();//獲取原始請求
                Request.Builder requestBuilder = originalRequest.newBuilder() //建立新的請求
                        .addHeader("Accept", "application/json")
                        .addHeader("Content-Type", "application/json; charset=utf-8")
                        .removeHeader("User-Agent")
                        .addHeader("User-Agent",BaseUtils.getUserAgent())
                        .method(originalRequest.method(),originalRequest.body());
                return chain.proceed(requestBuilder.build()); //重新請求

  • 全域性動態新增Token
 Interceptor tokenInterceptor = new Interceptor() {  //全域性攔截器,往請求頭部新增 token 欄位,實現了全域性新增 token
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();//獲取原始請求
                Request.Builder requestBuilder = originalRequest.newBuilder() //建立新的請求
                        .addHeader("Accept", "application/json")
                        .addHeader("Content-Type", "application/json; charset=utf-8")
                        .removeHeader("User-Agent")
                        .addHeader("User-Agent",BaseUtils.getUserAgent())
                        .method(originalRequest.method(),originalRequest.body());
//                Log.e("----------------",originalRequest.body().toString());
                Request tokenRequest = null; //帶有token的請求
                if (StringUtils.isEmpty(App.mmkv.decodeString(BaseConfig.TOKEN,null))){
                    return chain.proceed(requestBuilder.build());
                }

                tokenRequest = requestBuilder
                        .header("Authorization","Bearer "+App.mmkv.decodeString(BaseConfig.TOKEN,null))
                        .build();
                return chain.proceed(tokenRequest);
            }
        };

這裡使用了騰訊的MMKV框架進去本地儲存Token,因為我們一開始是沒有拿到Token的,所以需要進行動態新增

  • 自動判斷token是否過期,過期無感重新整理

在進行網路互動的時候,服務端簽發的Token是有時效性的而且一般比較短,過了有效期就需要重新請求,而這個過程我們不能讓使用者察覺到,所以需要實現使用者無感知的情況下重新整理請求新的Token.

  Interceptor tokenRespInterceptor = new Interceptor() { //攔截返回體  判斷token是否過期.過期重寫拉取新的token
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);
//                KLog.d( response.body().string());
                if (isTokenExpired(response)){
                    KLog.d( "自動重新整理Token,然後重新請求資料");
                    //同步請求方式,獲取最新的Token
                    String newToken = getNewToken();
                   if (newToken != null){
                        //使用新的Token,建立新的請求
                        Request newRequest = chain.request()
                                .newBuilder()
                                .header("Authorization", "Bearer " + newToken)
                                .build();
                        //重新請求
                        return chain.proceed(newRequest);
                    }

                }
                return response.newBuilder().body(ResponseBody.create(MediaType.parse("UTF-8"),body)).build();
            }
        };


這裡需要根據服務端約定好的過期規則進去判斷,這裡簡單示範一下



    /**
     * 根據Response,判斷Token是否失效
     *
     * @param response
     * @return
     */
    private boolean isTokenExpired(Response response) {
        try {
            body = response.body().string();
            JSONObject object = new JSONObject(body);
            message = object.getString("Message");
            code = object.getInt("Code");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if ("Token is expired".equals(message)&& code == 1) {
            return true;
        }
        return false;
    }

獲取新的Token

  /**
     * 同步請求方式,獲取最新的Token
     *
     * @return
     */
    private String getNewToken() {
        // 透過獲取token的介面,同步請求介面
        GetRequest_Interface request = new Retrofit.Builder()
                .baseUrl(API.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build().create(GetRequest_Interface.class);
//        KLog.e(Remember.getString("refresh_token",null));
        RequestBody body = BaseUtils.convertJson(BaseUtils.paramsMap("refresh_token",App.mmkv.decodeString(BaseConfig.REFRESH_TOKEN,null)));
        Call<RefreshToken> call = request.postRefreshToken(body);
        try {
            response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        KLog.e(response.body().getCode()+response.body().getMessage());

        if (response.code() == 200 &&response.body().getCode() ==0){
            newToken = response.body().getData().getToken();
            KLog.e("---------------"+newToken);
            App.mmkv.encode(BaseConfig.TOKEN,newToken);
            App.mmkv.encode(BaseConfig.SCHOOL_TOKEN,response.body().getData().getSchool_token());
            App.mmkv.encode(BaseConfig.EXPIRE_IN,response.body().getData().getExpire_in());
        }

        return newToken;
    }

  • 初始化 配置 Retrofit
  retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(API.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

至此,關於網路請求的相關引數資訊就基本配置完成

將上述配置步驟進行封裝


/**
 * Created by darryrzhong
 * 
 *
 * 統一的Retrofit入口
 */
public class RetrofitHelper {

    private static final int DEFAULT_TIME_OUT = 5;//超時時間 5s
    private static final int DEFAULT_READ_TIME_OUT = 10;
    private final Retrofit retrofit;
    private String body;
    private retrofit2.Response<RefreshToken> response;
    private String newToken;
    private String message;
    private int code;

    private RetrofitHelper(){


        Interceptor tokenInterceptor = new Interceptor() {  //全域性攔截器,往請求頭部新增 token 欄位,實現了全域性新增 token
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();//獲取原始請求
                Request.Builder requestBuilder = originalRequest.newBuilder() //建立新的請求
                        .addHeader("Accept", "application/json")
                        .addHeader("Content-Type", "application/json; charset=utf-8")
                        .removeHeader("User-Agent")
                        .addHeader("User-Agent",BaseUtils.getUserAgent())
                        .method(originalRequest.method(),originalRequest.body());
//                Log.e("----------------",originalRequest.body().toString());
                Request tokenRequest = null; //帶有token的請求
                if (StringUtils.isEmpty(App.mmkv.decodeString(BaseConfig.TOKEN,null))){
                    return chain.proceed(requestBuilder.build());
                }

                tokenRequest = requestBuilder
                        .header("Authorization","Bearer "+App.mmkv.decodeString(BaseConfig.TOKEN,null))
                        .build();
                return chain.proceed(tokenRequest);
            }
        };


        Interceptor tokenRespInterceptor = new Interceptor() { //攔截返回體  判斷token是否過期.過期重寫拉取新的token
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);
//                KLog.d( response.body().string());
                if (isTokenExpired(response)){
                    KLog.d( "自動重新整理Token,然後重新請求資料");
                    //同步請求方式,獲取最新的Token
                    String newToken = getNewToken();
                    if (newToken != null){
                        //使用新的Token,建立新的請求
                        Request newRequest = chain.request()
                                .newBuilder()
                                .header("Authorization", "Bearer " + newToken)
                                .build();
                        //重新請求
                        return chain.proceed(newRequest);
                    }
                }
                return response.newBuilder().body(ResponseBody.create(MediaType.parse("UTF-8"),body)).build();
            }
        };


        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)  //l 連線超時時間
                .writeTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS)  //讀寫超時
                .readTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS)   //讀取超時
                .retryOnConnectionFailure(true)  //失敗重連
                .addNetworkInterceptor(tokenInterceptor)  //新增網路攔截器
                .addInterceptor(tokenRespInterceptor)
                //.authenticator(authenticator)  //授權認證
                .build();


        retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(API.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();



    }


    /**
     * 同步請求方式,獲取最新的Token
     *
     * @return
     */
    private String getNewToken() {
        // 透過獲取token的介面,同步請求介面
        GetRequest_Interface request = new Retrofit.Builder()
                .baseUrl(API.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build().create(GetRequest_Interface.class);
        RequestBody body = BaseUtils.convertJson(BaseUtils.paramsMap("refresh_token",App.mmkv.decodeString(BaseConfig.REFRESH_TOKEN,null)));
        Call<RefreshToken> call = request.postRefreshToken(body);
        try {
            response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        KLog.e(response.body().getCode()+response.body().getMessage());

        if (response.code() == 200 &&response.body().getCode() ==0){
            newToken = response.body().getData().getToken();
            KLog.e("---------------"+newToken);
            App.mmkv.encode(BaseConfig.TOKEN,newToken);
            App.mmkv.encode(BaseConfig.SCHOOL_TOKEN,response.body().getData().getSchool_token());
            App.mmkv.encode(BaseConfig.EXPIRE_IN,response.body().getData().getExpire_in());
        }

        return newToken;
    }


    /**
     * 根據Response,判斷Token是否失效
     *
     * @param response
     * @return
     */
    private boolean isTokenExpired(Response response) {
        try {
            body = response.body().string();
            JSONObject object = new JSONObject(body);
            message = object.getString("Message");
            code = object.getInt("Code");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if ("Token is expired".equals(message)&& code == 1) {
            return true;
        }
        return false;
    }


    private static class SingletonHolder{
        private static final RetrofitHelper INSTANCE = new RetrofitHelper();
    }

    /**
     * 獲取RetrofitServiceManager
     * @return
     */
    public static RetrofitHelper getInstance(){
        return SingletonHolder.INSTANCE;
    }



   /**
     * 獲取對應的Service
     * @param service Service 的 class
     * @param <T>
     * @return
     */
    public  <T> T create(Class<T> service){
        return retrofit.create(service);
    }
    

}

如果業務中有多個BaseUrl的話,可以直接寫個方法暴露出去就好了.

統一處理請求結果的BaseObserver

  • 首先建立一個請求結果的回撥 ResponseCallBack
public interface ResponseCallBack<T> {
    void onSuccess(T t);

    void onFault(String errorMsg);
}

  • 建立一個請求開始時的初始化回撥 ProgressListener
public interface ProgressListener {
    void startProgress();

    void cancelProgress();
}


  • 建立統一處理結果的BaseObserver

建立BaseObserver,在回撥中進行業務處理

public  class BaseObserver<T> implements Observer<T> {
        private ResponseCallBack responseCallBack;
        private ProgressListener progressListener;
        private Disposable disposable;

public BaseObserver(ResponseCallBack responseCallBack,ProgressListener progressListener){
     this.responseCallBack = responseCallBack;
     this.progressListener = progressListener;
 }


}

在 onSubscribe () 方法中進行請求開始時的初始化操作

  @Override
    public void onSubscribe(Disposable d) {
         this.disposable = d;
         if (progressListener != null){
             progressListener.startProgress();
         }
    }

在 onNext () 方法中處理請求成功業務

@Override
    public void onNext(T t) {
         responseCallBack.onSuccess(t);
    }

在onError () 方法中統一處理請求失敗資訊

 @Override
    public void onError(Throwable e) {
        KLog.d(e.getMessage());
        try {

            if (e instanceof SocketTimeoutException) {//請求超時
                responseCallBack.onFault("請求超時,請稍後再試");
            } else if (e instanceof ConnectException) {//網路連線超時
                responseCallBack.onFault("網路連線超時,請檢查網路狀態");
            } else if (e instanceof SSLHandshakeException) {//安全證照異常
                responseCallBack.onFault("安全證照異常");
            } else if (e instanceof HttpException) {//請求的地址不存在
                int code = ((HttpException) e).code();
                if (code == 504) {
                    responseCallBack.onFault("網路異常,請檢查您的網路狀態");
                } else if (code == 404) {
                    responseCallBack.onFault("請求的地址不存在");
                } else {
                    responseCallBack.onFault("請求失敗");
                }
            } else if (e instanceof UnknownHostException) {//域名解析失敗
                responseCallBack.onFault("域名解析失敗");
            } else {
                responseCallBack.onFault("error:" + e.getMessage());
            }
        } catch (Exception e2) {
            e2.printStackTrace();
        } finally {
            Log.e("OnSuccessAndFaultSub", "error:" + e.getMessage());
            if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
                disposable.dispose();
            }
            if (progressListener!=null){
                progressListener.cancelProgress();
            }
        }
    }

在 onComplete () 中處理請求成功結束後的業務

 @Override
    public void onComplete() {
        if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
            disposable.dispose();
        }
        if (progressListener!=null){
            progressListener.cancelProgress();
        }
    }

程式碼如下:

/**
 * Created by darryrzhong
 * 
 */


public  class BaseObserver<T> implements Observer<T> {

 private ResponseCallBack responseCallBack;
 private ProgressListener progressListener;
 private Disposable disposable;

 public BaseObserver(ResponseCallBack responseCallBack,ProgressListener progressListener){
     this.responseCallBack = responseCallBack;
     this.progressListener = progressListener;
 }

    @Override
    public void onSubscribe(Disposable d) {
         this.disposable = d;
         if (progressListener != null){
             progressListener.startProgress();
         }
    }


    @Override
    public void onNext(T t) {
         responseCallBack.onSuccess(t);
    }

    @Override
    public void onError(Throwable e) {
        KLog.d(e.getMessage());
        try {

            if (e instanceof SocketTimeoutException) {//請求超時
                responseCallBack.onFault("請求超時,請稍後再試");
            } else if (e instanceof ConnectException) {//網路連線超時
                responseCallBack.onFault("網路連線超時,請檢查網路狀態");
            } else if (e instanceof SSLHandshakeException) {//安全證照異常
                responseCallBack.onFault("安全證照異常");
            } else if (e instanceof HttpException) {//請求的地址不存在
                int code = ((HttpException) e).code();
                if (code == 504) {
                    responseCallBack.onFault("網路異常,請檢查您的網路狀態");
                } else if (code == 404) {
                    responseCallBack.onFault("請求的地址不存在");
                } else {
                    responseCallBack.onFault("請求失敗");
                }
            } else if (e instanceof UnknownHostException) {//域名解析失敗
                responseCallBack.onFault("域名解析失敗");
            } else {
                responseCallBack.onFault("error:" + e.getMessage());
            }
        } catch (Exception e2) {
            e2.printStackTrace();
        } finally {
            Log.e("OnSuccessAndFaultSub", "error:" + e.getMessage());
            if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
                disposable.dispose();
            }
            if (progressListener!=null){
                progressListener.cancelProgress();
            }
        }
    }


    @Override
    public void onComplete() {
        if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
            disposable.dispose();
        }
        if (progressListener!=null){
            progressListener.cancelProgress();
        }
    }
}

至此,統一處理結果的BaseObserver封裝完畢

其他 (請求傳參,返回JSON)

這裡根據服務端接收資料不同而有不同方式,想要了解更多傳參方式,可以自行去了解Retrofit的註解,這裡只介紹向服務端傳遞Json資料.

  • Json 資料格式
    首先我們看一下標準的Json資料格式
返回體:

{
    "Code": 0,
    "Data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC*********",
        "refresh_token": "c9ced011-***************************",
        "expire_in": 1560330991,
        "student_id": 33
    },
    "Message": "登入成功"
}

請求體:

{"phone":"13145214436","id":"12345"}

返回體的資料解析就不說了,說說請求體怎麼傳遞

首先我們把最外面的{ } json 物件當做是一個 map 物件,這樣是不是一下子就知道怎麼轉化了,對的,就是你想的那樣.

 HashMap<String, Object> map = new HashMap<>();
        map.put("phone", "13145214436");
        map.put("id", "12345");

然後把map物件轉化成json字串,傳給服務端就行了

        Gson gson=new Gson();
        String strEntity = gson.toJson(map);
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json;charset=UTF-8"),strEntity);

至於更復雜的請求體也是一樣的做法

{
  "school_id":1,
  "student_id":23,
  "start_time":"2019-05-10 15:04:05",
  "end_time":"2019-06-10 15:04:05",
  "points": [
                {
                    "longitude": 0,
                    "latitude": 0
                },
                {
                    "longitude": 0,
                    "latitude": 0
                },
                {
                    "longitude": 0,
                    "latitude": 0
                },
                {
                    "longitude": 0,
                    "latitude": 0
                }
            ]
  
}

上面的請求體中有個json陣列,陣列裡面又巢狀了json物件,還是一樣的做法
把json陣列看成是一個list,對的,有和上面一樣的套路了是不是很簡單,

使用示例

這裡以登入業務做個簡單示範


  GetRequest_Interface request = RetrofitHelper.getInstance().create(GetRequest_Interface.class);  //request請求入口

  HashMap<String,Object> params = new HashMap();
            params.put("phone",phone);
            params.put("id",id);
  RequestBody body = BaseUtils.convertJson(params);

  Observable<RegistLogin> observable= request.postRegist(body);

 observable.subscribeOn(Schedulers.io())
                      .observeOn(AndroidSchedulers.mainThread())
                      .subscribe(new BaseObserver<RegistLogin>(new ResponseCallBack<RegistLogin>() {
                          @Override
                          public void onSuccess(RegistLogin registLogin) {
                          //此處處理 code == 200 
                          }

                          @Override
                          public void onFault(String errorMsg) {
                               BaseUtils.showToast(mContext,errorMsg);
                          }
                      }, new ProgressListener() {
                          @Override
                          public void startProgress() {
                              dialog = BaseUtils.showSpotsDialog(mContext,"登入中");
                              dialog.show();
                          }

                          @Override
                          public void cancelProgress() {
                             dialog.dismiss();
                          }
                      }));

這樣一來,是不是程式碼明瞭簡潔,程式碼質量明顯提高了一個層次

至此,Rxjava 和 Retrofit 結合使用 與封裝就基本完成了,再次說明一下,沒有最完美的封裝,只有最適合自己業務的封裝,所以,如果需要請進行自己的業務定製,這裡只提供思路.

歡迎關注作者,更多幹貨等你來拿喲.

請賞個小紅心!因為你的鼓勵是我寫作的最大動力!

更多精彩文章請關注

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4650/viewspace-2823225/,如需轉載,請註明出處,否則將追究法律責任。

相關文章