Retrofit2.0+RxJava2.0封裝使用

Retrofit2.0+RxJava2.0

大家都知道Okhttp3用著已經很順手了,Retrofit則是在OkHttp3的基礎上,對網絡請求做了更加系統、簡潔的封裝,使用面向接口的方式進行網絡請求,它使用動態生成的代理類封裝了接口,而且使用很多注解提供功能。RxJava就是一種用Java語言實現的響應式編程,是一個基于事件訂閱的異步執行的一個類庫,核心思想是觀察者模式。

其它文章

Android MVP模式簡單使用和封裝使用
OkHttp3簡單使用和封裝使用
Android開發 多語言、指紋登錄、手勢登錄
Android使用IconFont阿里矢量圖標
Android Studio 使用SVN 主干和分支合并代碼

Retrofit、RxJava基礎

如果你還不了解Retrofit、RxJava的話,請先查看一些基礎文章,我也是這么過來的。

效果圖
效果圖.gif

項目地址:https://github.com/pengjunshan/Retrofit2RxJava2

配置
  • gradle中引入相關庫 (相關庫請到github上查看最新版本)
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
  • 自動下載Okhttp3和okio資源


    Libraries資源.png
  • 權限 (這里只是demo所用到的權限,實際根據項目所用配置)
  <!--聯網權限-->
  <uses-permission android:name="android.permission.INTERNET"></uses-permission>
  <!--檢測網絡狀態權限-->
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  <!-- 在SDCard中創建與刪除文件權限 -->
  <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
    tools:ignore="ProtectedPermissions"/>
  <uses-permission android:name="android.permission.WRITE_SETTINGS"
    tools:ignore="ProtectedPermissions"/>
  <!-- 往SDCard寫入數據權限 -->
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <!-- 從SDCard讀取數據權限 -->
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
先看下代碼調用和日志

案例中分別寫了封裝之前和封裝之后調的使用,封裝后比封裝前代碼少了不止一點兩點,但是該有的流程一步也不少只不過封裝起來沒有看到而已。

封裝之前簡單使用分為4步完成

  • 創建Retrofit
  • 構建接口
  • 構建被觀察者對象
  • 訂閱
/**
   * 封裝之前
   * 簡單使用
   */
  public void RetrofitRxJavaRequet(View view) {
    //第一步:創建Retrofit
    Retrofit mRetrofit = new Builder()
        .baseUrl(Constants.BASEURL)//添加BaseUrl
        .client(MyApplication.mOkHttpClient)//添加OkhttpClient
        .addConverterFactory(GsonConverterFactory.create())//添加Gson解析
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//  添加 rxjava 支持
        .build();

    //第二步:構建接口
    IApiService iApiService = mRetrofit.create(IApiService.class);

    //第三步:構建被觀察者對象
    Observable<BaseBean<List<BannerBean>>> observable = iApiService.getBanner();

    //第四步:訂閱
    observable.subscribeOn(Schedulers.io())//指定Observable自身在io線程中執行
        .observeOn(AndroidSchedulers.mainThread())//指定一個觀察者在主線程中國觀察這個Observable
        .subscribe(new Observer<BaseBean<List<BannerBean>>>() {
          @Override
          public void onSubscribe(Disposable d) {
            Log.e("TAG", "開始之前");
          }

          @Override
          public void onNext(BaseBean<List<BannerBean>> listBaseBean) {
            Toast.makeText(MainActivity.this,  listBaseBean.getData().toString(), Toast.LENGTH_SHORT).show();
            Log.e("TAG", "成功");
          }

          @Override
          public void onError(Throwable e) {
            Toast.makeText(MainActivity.this, "失敗", Toast.LENGTH_SHORT).show();
            Log.e("TAG", "失敗");
          }

          @Override
          public void onComplete() {
            Log.e("TAG", "結束");
          }
        });

  }

  /**
   * 封裝后使用
   * GET請求
   */
  public void GetRequet(View view) {

    RetrofitRequest.getBannerApi(context, new IResponseCallBack<BaseBean<List<BannerBean>>>() {
      @Override
      public void onSuccess(BaseBean<List<BannerBean>> data) {
        Toast.makeText(MainActivity.this, "banner成功="+data.getData().toString(), Toast.LENGTH_SHORT).show();
        Log.e("TAG", "banner內容="+data.getData().toString());
      }

      @Override
      public void onFail(OkHttpException failuer) {
        Toast.makeText(MainActivity.this, "失敗", Toast.LENGTH_SHORT).show();
        Log.e("TAG", "失敗="+failuer.getEmsg());
      }
    });

  }

  /**
   * 封裝后使用
   * POST請求
   */
  public void PostKeyValueRequet(View view) {

    Map<String,String> map  = new ArrayMap<>();
    map.put("username", "15294792877");
    map.put("password", "15294792877pp");
    RetrofitRequest.postLoginApi(context, map, new IResponseCallBack<BaseBean<LoginBean>>() {
      @Override
      public void onSuccess(BaseBean<LoginBean> data) {
        Toast.makeText(MainActivity.this, "登錄成功="+data.getData().toString(), Toast.LENGTH_SHORT).show();
        Log.e("TAG", "登錄成功="+data.getData().toString());
      }

      @Override
      public void onFail(OkHttpException failuer) {
        Toast.makeText(MainActivity.this, "失敗", Toast.LENGTH_SHORT).show();
        Log.e("TAG", "失敗="+failuer.getEmsg());
      }
    });

  }

  /**
   * 封裝后使用
   * 下載圖片
   */
  public void GetImgRequet(View view) {

    RetrofitRequest.downImgApi(String.valueOf(System.currentTimeMillis()) + ".png",
        new IResponseByteCallBack() {
          @Override
          public void onSuccess(File file) {
            Toast.makeText(MainActivity.this, "圖片下載成功="+file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
            Log.e("TAG", "圖片下載成功="+file.getAbsolutePath());
          }

          @Override
          public void onFailure(String failureMsg) {
            Toast.makeText(MainActivity.this, "圖片下載失敗", Toast.LENGTH_SHORT).show();
            Log.e("TAG", "圖片下載失敗="+failureMsg);
          }
        });
  }
日志打印.png
封裝
創建RetrofitClient

為我們的Retrofit配置參數,使用靜態語句塊來配置,只執行一次,運行一開始就開辟了內存,內存放在全局。設置baseUrl、添加OkHttpClient、添加Gson解析、添加 rxjava 支持
request方法是一個公共請求方法接收一個被觀察者(Observable)
subscribeOn(Schedulers.io())指定Observable自身在io線程中執行,
observeOn(AndroidSchedulers.mainThread())指定一個觀察者在主線程中國觀察這個Observable,
subscribe(new CallBackObserver<T>(listener, context));訂閱

/**
 * @author:PengJunShan.
 * 時間:On 2019-05-06.
 * 描述:Retrofit對象
 */

public class RetrofitClient {

  public static Retrofit mRetrofit;

  /**
   * 為我們的Client配置參數,使用靜態語句塊來配置
   * 只執行一次,運行一開始就開辟了內存,內存放在全局
   */
  static {
    mRetrofit = new Retrofit.Builder()
        .baseUrl(Constants.BASEURL)//添加BaseUrl
        .client(MyApplication.mOkHttpClient)//添加OkhttpClient
        .addConverterFactory(GsonConverterFactory.create())//添加Gson解析
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//  添加 rxjava 支持
        .build();
  }

  /**
   * 所有請求都放在一個接口中
   */
  public static IApiService createApi() {
    return mRetrofit.create(IApiService.class);
  }

  /**
   * 不同請求放不同接口中
   * @param service 指定接口
   */
  public static <T> T createApi(Class<T> service) {
    return mRetrofit.create(service);
  }


  /**
   * @param context 上下文
   * @param observable 被觀察者
   * @param listener 回調接口
   * @param requstName 接口名稱
   * @param <T> 實體類
   */
  public static <T> void request(Context context, Observable<T> observable,
      final IResponseCallBack<T> listener, String requstName) {
    Constants.requestName = requstName;
    if (!Utils.isConnected(MyApplication.context)) {
      if (listener != null) {
        listener.onFail(new OkHttpException(-1, "網絡不可用,請檢查網絡"));
      }
      return;
    }
    observable.subscribeOn(Schedulers.io())//指定Observable自身在io線程中執行
        .observeOn(AndroidSchedulers.mainThread())//指定一個觀察者在主線程中國觀察這個Observable
        .subscribe(new CallBackObserver<T>(listener, context));//訂閱

  }

  /**
   * 統一下載圖片共用
   * @param observable 被觀察者
   * @param callback 回調接口
   * @param imgPath 存儲地址
   * @param requstName 功能名稱
   */
  public static void downImg(Observable<ResponseBody> observable,
      final IResponseByteCallBack callback, final String imgPath, String requstName) {
    Constants.requestName = requstName;
    observable.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<ResponseBody>() {
          @Override
          public void onSubscribe(Disposable d) {
          }

          @Override
          public void onNext(ResponseBody responseBody) {
            File file = null;
            try {
              InputStream is = responseBody.byteStream();
              int len = 0;
              // 文件夾路徑
              String pathUrl =
                  Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                      + "/PJS/";
              File filepath = new File(pathUrl);
              if (!filepath.exists()) {
                filepath.mkdirs();// 創建文件夾
              }
              file = new File(pathUrl, imgPath);

              FileOutputStream fos = new FileOutputStream(file);

              byte[] buf = new byte[2048];
              while ((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
              }
              fos.flush();
              fos.close();
              is.close();
              callback.onSuccess(file);
            } catch (final Exception e) {
              callback.onFailure(e.getMessage());
            }
          }

          @Override
          public void onError(Throwable e) {
            callback.onFailure(e.getMessage());
          }

          @Override
          public void onComplete() {
          }
        });

  }

}
初始化OkHttpClient

前面介紹已經說過了Retrofit是對OkHttp3進行的封裝,所以網絡請求核心還是OkHttp3,需要對OkHttp3進行初始化配置。

/**
 * 作者:PengJunShan.
 * 時間:On 2019-05-06.
 * 描述:初始化OkHttpClient  當然也可以在RetrofitCLient類中初始化
 */
public class MyApplication extends Application {

  public static Context context;
  public static OkHttpClient mOkHttpClient;
  /**
   * 超時時間
   */
  private static final int TIME_OUT = 30;

  @Override
  public void onCreate() {
    super.onCreate();
    context = getApplicationContext();
    initOkHttp();
  }

  private void initOkHttp() {
    //獲取緩存路徑
    File cacheDir = MyApplication.context.getExternalCacheDir();

    //設置緩存的大小
    int cacheSize = 10 * 1024 * 1024;
    //創建我們Client對象的構建者
    OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
    okHttpBuilder
        //為構建者設置超時時間
        .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
        .readTimeout(TIME_OUT, TimeUnit.SECONDS)
        .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
        ////websocket輪訓間隔(單位:秒)
        .pingInterval(20, TimeUnit.SECONDS)
        //設置緩存
        .cache(new Cache(cacheDir.getAbsoluteFile(), cacheSize))
        //允許重定向
        .followRedirects(true)
        //設置攔截器
        .addInterceptor(new RequetInterceptor())
        //添加https支持
        .hostnameVerifier(new HostnameVerifier() {
          @Override
          public boolean verify(String s, SSLSession sslSession) {
            return true;
          }
        })
        .sslSocketFactory(HttpsUtils.initSSLSocketFactory(), HttpsUtils.initTrustManager());

    mOkHttpClient = okHttpBuilder.build();

  }

}
公共Observer 觀察者

創建一個公用的Observer來處理請求結果后進行回調,在回調數據之前先判斷當前Activity是否還存在。在onSubscribe()方法里可以進行網絡加載圈,onComplete()方法里可以取消網絡加載圈,把錯誤交給ExceptionHandle類處理。

/**
 * @author:PengJunShan.
 *
 * 時間:On 2019-05-06.
 *
 * 描述:公共Observer 觀察者
 */
public class CallBackObserver<T> implements Observer<T> {

  private IResponseCallBack<T> mListener;
  private Activity activity;

  public CallBackObserver(IResponseCallBack<T> listener, Context context) {
    this.mListener = listener;
    this.activity = (Activity) context;
  }

  @Override
  public void onSubscribe(Disposable d) {
    /**
     * 這里可以 顯示加載圈等
     */
  }

  /**
   * 成功
   * @param data
   */
  @Override
  public void onNext(T data) {
    if (mListener != null && !activity.isFinishing()) {
      BaseBean baseBean = (BaseBean) data;
      /**
       * 是否成功
       */
      if (baseBean.isSuccess()) {
        mListener.onSuccess(data);
      }else {
        mListener.onFail(new OkHttpException(baseBean.getErrorCode(), baseBean.getErrorMsg()));
      }
    }

  }

  /**
   * 失敗
   * @param e
   */
  @Override
  public void onError(Throwable e) {
    onComplete();
    if (mListener != null && !activity.isFinishing()) {
      /**
       * 處理失敗原因
       */
      OkHttpException okHttpException = ExceptionHandle.handleException(e,activity);
      if(okHttpException !=null) {
        mListener.onFail(okHttpException);
      }
    }

  }

  @Override
  public void onComplete() {
    /**
     * 這里可以 關閉加載圈等
     */
  }

}
ExceptionHandle(自定義錯誤解析類)

除了網絡錯誤之外,我們還要解析前端和后端定義的錯誤碼,比如常見的token失效錯誤,我們抓取錯誤碼來進行重新登錄獲取新的token值。

/**
 * @author:PengJunShan.
 * 時間:On 2019-05-06.
 * 描述:自定義錯誤解析類
 */
public class ExceptionHandle {

  private static final int TIMEOUT_ERROR = -1;
  private static final int JSON_ERROR = -2;
  private static final int NETWORK_ERROR = -3;
  private static final int OTHER_ERROR = -4;

  private static final String TIMEOUTMSG = "請求超時";
  private static final String JSONMSG = "解析錯誤";
  private static final String NETWORKMSG = "連接失敗";
  private static final String OTHERMSG = "未知錯誤";

  /**
   * 根據接口定義 錯誤碼等于999 為token失效 需要重新登錄獲取新的token
   */
  private static final int TOKENLOGIN = 999;

  public static OkHttpException handleException(Throwable e, Activity activity) {
    OkHttpException ex = null;
    if (e instanceof HttpException) {
      ResponseBody body = ((HttpException) e).response().errorBody();
      try {
        Gson gson = new GsonBuilder().serializeNulls().create();
        String jsonStr = body.string();
        BaseBean baseBean = gson.fromJson(jsonStr, BaseBean.class);

        /**
         * token失效 重新登錄
         */
        if (baseBean.getErrorCode() == TOKENLOGIN ) {
//          activity.startActivity(LoginActivity.class);
//          activity.finish();
        } else {
          ex = new OkHttpException(baseBean.getErrorCode(), baseBean.getErrorMsg());
        }
      } catch (IOException e1) {
        e1.printStackTrace();
      }
      return ex;
    } else if (e instanceof java.net.SocketTimeoutException) {
      ex = new OkHttpException(TIMEOUT_ERROR, TIMEOUTMSG);
      return ex;
    } else if (e instanceof JsonParseException
        || e instanceof JSONException
        || e instanceof ParseException) {
      ex = new OkHttpException(JSON_ERROR, JSONMSG);
      return ex;
    } else if (e instanceof ConnectException) {
      ex = new OkHttpException(NETWORK_ERROR, NETWORKMSG);
      return ex;
    } else {
      ex = new OkHttpException(OTHER_ERROR, OTHERMSG);
      return ex;
    }
  }

}

/**
 * @author:PengJunShan.
 * 時間:On 2019-05-06.
 * 描述:自定義異常類,返回ecode,emsg到業務層
 */

public class OkHttpException extends Exception {

  private static final long serialVersionUID = 1L;

  private int ecode;
  private String emsg;

  public OkHttpException(int ecode, String emsg) {
    this.ecode = ecode;
    this.emsg = emsg;
  }

  public int getEcode() {
    return ecode;
  }

  public String getEmsg() {
    return emsg;
  }

}
IResponseCallBack<T>(回調接口)

Retrofit支持Gson解析實體類,請求成功后把實體類我們自定義的接口回調。失敗的話就回調護理過的錯誤實體類。

/**
 * @author:PengJunShan.
 * 時間:On 2019-05-06.
 * 描述:自定義回調接口
 */

public interface IResponseCallBack<T> {

  void onSuccess(T data);

  void onFail(OkHttpException failuer);

}
RequetInterceptor(攔截器)

攔截器的作用還是很大的,一般我們工作中請求頭部都會傳入token、用戶id標識認證參數。連接器中可以攔截到Request對象,然后添加頭部公共參數。通過獲取FormBody可以打印出入參參數,通過獲取Response可以打印出出參參數。不能直接使用response.body().string()的方式輸出日志,因為response.body().string()之后,response中的流會被關閉,我們需要創建出一個新的response給應用層處理。

/**
 * @author:PengJunShan.
 * 時間:On 2019-05-05.
 * 描述:日志攔截器
 */

public class RequetInterceptor implements Interceptor {

  /**
   * 這個chain里面包含了request和response,所以你要什么都可以從這里拿
   */
  @Override
  public Response intercept(Chain chain) throws IOException {

    /**
     * 可以添加公共頭部參數如token
     */
    Request request = chain.request()
        .newBuilder()
//        .header("TOKEN", token)
//        .header("ID", id)
        .build();

    /**
     * 開始時間
     */
    long startTime = System.currentTimeMillis();
    Log.e("TAG","\n"+"requestUrl=" + request.url());
    String method = request.method();

    if ("POST".equals(method)) {
      try {
        JSONObject jsonObject = new JSONObject();
        if (request.body() instanceof FormBody) {
          FormBody body = (FormBody) request.body();
          for (int i = 0; i < body.size(); i++) {
            jsonObject.put(body.encodedName(i), body.encodedValue(i));
          }
          Log.e("TAG","入參JSON= " + jsonObject.toString());
        }
      } catch (JSONException e) {
        e.printStackTrace();
      }
    }

    Response response = chain.proceed(request);
    /**
     * 這里不能直接使用response.body().string()的方式輸出日志
     * 因為response.body().string()之后,response中的流會被關閉,程序會報錯,我們需要創建出一個新的response給應用層處理
     */
    ResponseBody responseBody = response.peekBody(1024 * 1024);
    Log.e("TAG","出參JSON=" + responseBody.string());
    long endTime = System.currentTimeMillis();
    long duration = endTime - startTime;
    Log.e("TAG","----------" + Constants.requestName + "耗時:" + duration + "毫秒----------");
    return response;
  }

}
結束

網上有很多別人封裝好的庫,但是符不符合自己的項目使用就是另外一回事了。還不如自己花點時間封裝一個既簡單又符合自己項目使用的代碼,文章中貼出來的代碼是核心代碼并不是所有的代碼,下載代碼根據自己項目需求修改一下就可以用的。下篇我要寫一個以MVP+retrofit2+rxjava2進行網絡請求封裝,主要是講MVP模式。

項目地址:https://github.com/pengjunshan/Retrofit2RxJava2

拿到代碼后移到自己項目中根據自己項目需求修改即可使用。

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

推薦閱讀更多精彩內容