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

前言

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

基本使用

定義請求接口

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 字符串爲準git

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了,也不符合編程規範.json

因此基於你會熟練的使用了的前提下,咱們就須要將以上代碼進行簡單封裝.segmentfault

關於封裝我想多說一句api

對於封裝,不少人都認爲封裝就是使代碼足夠簡潔,邏輯足夠清晰,符合開閉原則等,的確是這樣的,可是須要使狀況而定,若是你寫的代碼是服務廣大人羣,也就是開源項目,那就要考慮不少因素,作到足夠開放.數組

但若是隻是用到本身的項目裏,那咱們必需要明確一點,那就是定製化的前提是符合本身業務的需求,而不要過於封裝.因此也就有爲何咱們經常須要對別人的開源框架作二次封裝,就是這個道理,沒有最好的封裝,只有最合適的封裝.安全

封裝篇

Url統一存放

public interface API {

   //此處存放全部的BaseUrl
    String BASE_URL = "http://xx.xxx.xx.225:8080"; //核心後臺API
    String BASE_SCHOOL_URL = "http://xx.xxx.xx.225:8081"; //學校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的攔截器相關內容,不熟悉的能夠先去了解bash

  • 統一添加公共請求頭
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 結合使用 與封裝就基本完成了,再次說明一下,沒有最完美的封裝,只有最適合本身業務的封裝,因此,若是須要請進行本身的業務定製,這裏只提供思路.

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

請賞個小紅心!由於你的鼓勵是我寫做的最大動力!

更多精彩文章請關注

相關文章
相關標籤/搜索