天天看點

[從零開始系列]AndroidApp研發之路(一) 網絡請求的封裝(一)前言網絡請求封裝成果封裝細節關于我

文章目錄:

- 前言

- 封裝成果

- 封裝細節

- 如何使用

- 注意

- 作者

[從零開始系列]AndroidApp研發之路(一) 網絡請求的封裝(一)前言網絡請求封裝成果封裝細節關于我

前言

寫在前面

  • 技術選型
  • 元件化設計
  • ReactNative-Android 的簡單實踐
  • 阿裡Atlas(插件化)與該項目的簡單實踐
  • AndroidStudio插件快速實作元件化和MVP

集android技術于一體,你們想要的都在這裡

[從零開始系列]AndroidApp研發之路(一) 網絡請求的封裝(一)前言網絡請求封裝成果封裝細節關于我

項目 Github: https://github.com/chengzichen/Flyabbit

初衷:

之是以寫[從零開始]這系列,是技術更新的太快,導緻以前的一些編碼和設計不在适用目前公司情況,從零開始搭建起一個更适用目前公司的架構,但是更重要的一點是在一個團隊裡快速上手和熟悉一個新的各類架構,以及簡單原理.常言之授人予魚不如授人與漁,在這裡也是為了記錄這過程,也希望能給到大家一點幫助

  • [x] 從零開始的AndroidApp研發之路-網絡請求的封裝
  • [x] 從零開始的AndroidApp研發之路-<樓外篇>Retrofit的刨根問底篇
  • [x] AndroidStudio插件快速實作元件化和MVP
  • [ ] 從零開始的AndroidApp研發之路-MVP設計之路
  • [ ] 從零開始的AndroidApp研發之路-Base抽取之路
  • [ ] 從零開始的AndroidApp研發之路-元件化,持續內建
  • [ ] 從零開始的AndroidApp研發之路-單元測試

網絡請求

為什麼要封裝适合自己公司的網絡請求架構

雖然在是世面上有很多的成熟的網絡請求架構

比如:被Google在android6.0移除的HttpClent

,以及最古老也久遠的HttpUrlConnection,被我們抛棄的android-async-http,還有慢慢淡出我們視線的XUtil,2013年Google I/O大會上推出Volley 到現在的Retrofit +okHttp ,okGo,NoHttp等等,(這裡要感謝這些開源貢獻者,為Android生态注入了新鮮的血液),但是屬于适合自己的項目的可以是簡單封裝過的HttpClient, HttpUrlConnection,okhttp也可以是開箱即用的android-async-http,XUtil,Volley,Retrofit,okGo,NoHttp等等,我想說的是适合項目的是最好,這裡給大家做出一些參考

項目選擇請求架構: Retrofit

理由:

  1. 處理最快,預設使用Gson,Okhttp
  2. 項目背景人員普遍使用的RESTful 設計風格
  3. 可以通過工廠來生成CallAdapter,Converter,你可以使用不同的請求擴充卡(CallAdapter), 比方說RxJava,Java8, Guava。
  4. 可以使用不同的反序列化工具(Converter),比方說json, protobuff, xml, moshi等等

    注:

    RESTful:是指一種軟體架構風格,設計風格而不是标準,隻是提供了一組設計原則和限制條件組合使用
               

想要用Retrofi架構,最好是和RxJava聯用,否者和普通的網絡架構沒有優勢

注:

RxJava:是一個實作異步操作的庫
           

是以使用的是Retrofit+Rxjava +Okhttp+Gson這樣的一個組合進行封裝,從請求到異步到解析都有了

封裝成果

  1. Retrofit+Rxjava+okhttp基本使用方法
  2. 統一處理請求資料格式
  3. Cookie管理,Token重新整理的多種解決方案
  4. 多baseURl的處理
  5. 定制請求頭,定義請求體
  6. 傳回資料的統一處理
  7. 失敗後的retry封裝處理
  8. RxLifecycle管理生命周期,防止洩露
  9. 轉化器處理,多解析器(加密,解密)
  10. 檔案上傳
  11. 請求時菊花的控制

封裝細節

  • 導入依賴

    在app/build.gradle添加引用

`/*rx-android-java*/
    compile 'io.reactivex.rxjava2:rxandroid:+'
    compile 'io.reactivex.rxjava2:rxjava:+'
    compile 'com.tbruyelle.rxpermissions2:rxpermissions:+'
    compile 'com.trello:rxlifecycle-components:+'
    /*rotrofit*/
    compile 'com.squareup.retrofit2:retrofit:+'
    compile 'com.squareup.retrofit2:converter-gson:+'
    compile 'com.squareup.retrofit2:adapter-rxjava2+'
    compile 'com.google.code.gson:gson:+'




           

(這裡算是一個小技巧吧)為了以後拓展和更換網絡架構會把網絡請求封裝成一個Helper類并實作IDataHelper接口接口

内容為将要對外暴露的方法等.

public interface IDataHelper {

    void init(Context context);

    <S> S getApi(Class<S> serviceClass);

    <S> S createApi(Class<S> serviceClass);

    <S> S getApi(Class<S> serviceClass, OkHttpClient client);

    <S> S createApi(Class<S> serviceClass, OkHttpClient client);

    OkHttpClient  getClient();

    ..... 
}
           

(如果還沒有使用過Dagger2的同學就跳過吧)如果項目中使用了Dagger2依賴注入的話好處就展現出來了,以後更換的話架構的話隻需要更改IDataHelper的實作類就行了不用修改其他的代碼,在通過注入的方式注入到各個子產品中去.如下圖:

[從零開始系列]AndroidApp研發之路(一) 網絡請求的封裝(一)前言網絡請求封裝成果封裝細節關于我

或者使用簡單工廠設計模式

public class DataHelperProvider {
    private static IDataHelper dataHelper;
    public static IDataHelper getHttpHelper(Context context) {
        if (dataHelper == null) {
            synchronized (DataHelperProvider.class) {
                if (dataHelper == null) {
                    dataHelper = new HttpHelper(context);
                }
            }
        }
        return dataHelper;
    }

    .....

}
           

更換的時候隻需要更改IDataHelper的實作

HttpHelper分為 :

  • 請求相關:
  • Okhttp基礎相關的設定:
  • 正常設定: 連接配接逾時,cacheInterceptor緩存處理
  • 個性化設定: ClearableCookieJar管理,優化防重複,https,定制請求頭,定義請求體(加密,解密,)
  • 相關拓展支援

Okhttp基礎相關的設定

public OkHttpClient getOkHttpClient() {
        ClearableCookieJar cookieJar =//對cooke自動管理管理
                new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
        File cacheFile = new File(context.getExternalCacheDir(), "KairuCache");//緩存路徑
        Cache cache = new Cache(cacheFile,  *  * );//設定緩存大小為40M
        //緩存
        CacheInterceptor cacheInterceptor = new CacheInterceptor(context);
        //token管理
        TokenInterceptor tokenInterceptor = new TokenInterceptor();
        OkHttpClient.Builder builder =
                new OkHttpClient.Builder()
                        .cache(cache)//緩存大小的設定
                        .addInterceptor(cacheInterceptor) //對api緩存設定
                        .addNetworkInterceptor(cacheInterceptor)//對api緩存設定
                        .retryOnConnectionFailure(true)     //是否失敗重新請求連接配接
                        .connectTimeout(, TimeUnit.SECONDS)//連接配接逾時
                        .writeTimeout(, TimeUnit.SECONDS)//寫逾時逾時
                        .readTimeout(, TimeUnit.SECONDS)//讀逾時
                        .addInterceptor(tokenInterceptor)//token請求頭相關設定
                        .cookieJar(cookieJar);//cookie的本地儲存管理
        if (AppUtil.isDebug()) {//如果目前是debug模式就開啟日志過濾器
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(loggingInterceptor);
        }
        //目前okHttpClient
        okHttpClient = builder.build();

        return okHttpClient;
    }
           

==注意== :

-

retryOnConnectionFailure(true)

方法這裡的失敗重連的機制是:隻要網絡請求失敗後就會一直重連并不能滿足重連次數和時長的産品需求,實際項目中發現并不是那麼使用和靈活,是以這裡我還是放棄了該方法,(後續也許講到這裡)自己用Rxjava重新設計了一個能提供重連次數和重連時長的一個方法. (這裡的話還是要看實際的需求)

  • 如果背景使用是标準的Cookie安全認證機制話,認證Token是放在Cookie,那麼可嘗試使用ClearableCookieJar,添加依賴,位址 :PersistentCookieJar

但是沒聽見但是嗎? 實際上伺服器安全認證的方式多種多樣,Token送出存放方式也是多種多樣的,花樣百出,有沒有token的,有直接寫死,有放在Head裡面的,有當請求參數的,這些情況下Token過期了之後歐就需要自己去根據實際情況去重新整理擷取Token,往後的話也會一一介紹大部分的的技巧

Retrofit相關設定:

public Retrofit getRetrofit(String host) {
        if (gson == null)
            gson = new GsonBuilder().create();//Gson解析
        if (okHttpClient == null)
            okHttpClient = getOkHttpClient();
        retrofit = new Retrofit.Builder()
                .baseUrl(host)//baseurl路徑
                .client(okHttpClient)//添加okHttpClient用戶端
                .addConverterFactory(new StringConverterFactory())//添加String格式化工廠
                .addConverterFactory(GsonConverterFactory.create(gson))//添加Gson格式化工廠
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
        return retrofit;
    }
           

==說明== : 這裡提供了兩種資料解析方式: 解析為String類型和使用Gson解析成對應的Bean以便對應開發的實際需求,這裡也可以對Adapter再深一步的拓展在此就先不深入了

  • 多BaseURL的處理 : 在開發中有很多場景都會使用到不同的伺服器位址
public <S> S createApi(Class<S> serviceClass, OkHttpClient client) {
        String baseURL = "";
        try {
            Field field1 = serviceClass.getField("baseURL");
            baseURL = (String) field1.get(serviceClass);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.getMessage();
            e.printStackTrace();
        }
        if(retrofit!=null&&retrofit.baseUrl().host()==baseURL){
            return  retrofit.create(serviceClass);
        }else{

        return getRetrofit(baseURL).create(serviceClass);
        }
    }
           

==說明== : 這裡利用了反射原理拿到每個ApiService中寫好的BaseUrl,如果沒有的話就會使用同一的BaseUrl進行請求

  • 統一處理請求資料格式:

    如果說背景伺服器傳回的資料格式非常的規範的話,就可以嘗試将将一些公共部分抽取出來, 比如 : Code (這裡Code指的是業務代碼) 和 ErrorMessage(錯誤提示) 擷取其他的封裝到一個基類,這樣的好處:

    • 不用寫重複代碼
    • 業務代碼可以統一處理,比如:200成功,直接在基類裡處理一次即可,還比如後面提到的Token過期這些邏輯都能利用該操作完成.
public class ApiResponse<T> {
    public T data;
    public String code;
    public String message;
        ... get() set()
}
           

舉個簡單例子 :

比如 :

{code: ,
    message: success,
    loginInfo :{
        ...
    }}
           

這裡隻需要寫成,ApiResponse,實際的資料LoginInfoBean就在T data 裡擷取使用就行了了,對于Code在上一層就過濾處理完了.如果需要的話可以把泛型再抽出一個基類來,防止背景資料千變萬化呀

Rxjava的封裝

  • 線程排程:這裡使用到的是Rxjava2.0進行簡單封裝, 以前涉及到多線程切換都會想到Handler,AsyncTask,ThreadPool等等不同場合作用不同,當然有些很好的網絡庫也提供了相關的設計(Okgo等)
/**
      public static <T extends ApiResponse> rxSchedulerHelper<T, T> rxSchedulerHelper(int count,long delay) {    //compose簡化線程
        return new FlowableTransformer<T, T>() {
            @Override
            public Flowable<T> apply(Flowable<T> upstream) {
                return upstream.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }
           
  • BaseSubscriber (訂閱者的封裝)

BaseSubscriber繼承Rxjava2.0上ResourceSubscriber,這些都是支援背壓的,

像業務狀态碼統一處理,異常處理,Checkout,控制菊花等處理都放在了BaseSubscriber中去處理了,直接上代碼了也是比較簡單的.

public class BaseSubscriber<T extends ApiResponse> extends ResourceSubscriber<T> implements ProgressCancelListener {

    private static final String TAG = "BaseSubscriber";
    private SubscriberListener mSubscriberOnNextListener;
    private ProgressDialogHandler mHandler;
    Context aContext;

    /**
     * 該構造會出現一個自動彈出和消失的dialog,一般使用與通用情況,特殊情況請自行處理,也可以通過{@link SubscriberListener#isShowLoading()方法控制}
     *
     * @param mSubscriberOnNextListener
     * @param aContext
     */
    public BaseSubscriber(SubscriberListener mSubscriberOnNextListener, Context aContext) {
        this.mSubscriberOnNextListener = mSubscriberOnNextListener;
        mHandler = new ProgressDialogHandler(aContext, this, false);
        this.aContext = aContext;
    }

    /**
     * 使用該構造方法沒有LoadingDialog
     *
     * @param mSubscriberOnNextListener
     */
    public BaseSubscriber(SubscriberListener mSubscriberOnNextListener) {
        this.mSubscriberOnNextListener = mSubscriberOnNextListener;
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!NetworkUtil.isNetAvailable(AppContext.get())){
            ToastUtil.shortShow(AppContext.get(),"網絡錯誤,請檢查你的網絡");
            if (isDisposed())
                this.dispose();
            return;
        }
        if (mSubscriberOnNextListener != null && mSubscriberOnNextListener.isShowLoading())
            showProgressDialog();
        onBegin();
    }

    /**
     * 訂閱開始時調用
     * 顯示ProgressDialog
     */
    public void onBegin() {
        Log.i(TAG, "onBegin");
        if (mSubscriberOnNextListener != null) {
            mSubscriberOnNextListener.onBegin();
        }
    }

    private void showProgressDialog() {
        if (mHandler != null) {
            mHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
        }
    }

    private void dismissProgressDialog() {
        if (mHandler != null) {
            mHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
            mHandler = null;
        }
    }

    /**
     * 對錯誤進行統一處理
     * 隐藏ProgressDialog
     *
     * @param e
     */
    @Override
    public void onError(Throwable e) {
        Log.i(TAG, "onError:" + e.toString());
        if (mSubscriberOnNextListener != null) {
            mSubscriberOnNextListener.onError(e);
        }
        onComplete();
    }

    /**
     * 完成,隐藏ProgressDialog
     */
    @Override
    public void onComplete() {
        Log.i(TAG, "onCompleted");
        if (mSubscriberOnNextListener != null && mSubscriberOnNextListener.isShowLoading())
            dismissProgressDialog();
        if (mSubscriberOnNextListener != null) {
            mSubscriberOnNextListener.onCompleted();
        }
        if (!this.isDisposed()) {
            this.dispose();
        }
    }


    /**
     * 将onNext方法中的傳回結果交給Activity或Fragment自己處理,可以根據實際情況再封裝
     *
     * @param response 建立Subscriber時的泛型類型
     */
    @Override
    public void onNext(T response) {
        Log.i(TAG, "onNext");
        if (mSubscriberOnNextListener != null) {
            if (Constants.SUCCEED.equals(response.code)) {//成功
                mSubscriberOnNextListener.onSuccess(response.data);
            } else if (Constants.STATUS_RE_LOGIN.equals(response.code) || Constants.STATUS_NO_LOGIN.equals(response.code))//未登入或者登陸過期 {//判斷是否需要重新登入
                mSubscriberOnNextListener.checkReLogin(response.code, response.message);
            } else {//業務異常或者伺服器異常
                mSubscriberOnNextListener.onFail(response.code, response.message);
            }
        }
    }


    @Override
    public void onCancelProgress() {//取消菊花的轉動
        if (isDisposed())
            this.dispose();
        if (mHandler != null)
            mHandler = null;
    }
}
           
  • 異常統一解析: 服務異常處理,業務異常解析處理(Rxjava)

雖然到這裡一些常用網絡請求的功能也差不多了,但是在配合Rxjava使用的時候會出現一些小問題,下面将給出一點點的小技巧

public abstract class KrSubscriberListener<T> extends SubscriberListener<T> {

    public void onFail(String errorCode, String errorMsg) {
        //todo
    }


    @Override
    public void onError(Throwable e) {//這裡對異常重新定義,分為網絡異常,解析異常業務異常等
        NetError error = null;
        if (e != null) {
            if (!(e instanceof NetError)) {
                if (e instanceof UnknownHostException) {
                    error = new NetError(e, NetError.NoConnectError);
                } else if (e instanceof JSONException
                        || e instanceof JsonParseException
                        || e instanceof JsonSyntaxException) {
                    error = new NetError(e, NetError.ParseError);
                } else if (e instanceof SocketException
                        || e instanceof SocketTimeoutException) {
                    error = new NetError(e, NetError.SocketError);
                } else {
                    error = new NetError(e, NetError.OtherError);
                }
            } else {
                error = (NetError) e;
            }
            onFail(error.getType(), error.getMessage());
        }

    }

    @Override
    public void checkReLogin(String errorCode, String errorMsg) {
       //todo

    }
}
           
  • Token的重新整理問題

上面也說了認證和重新整理Token方式有很多種,這裡的話就挑常用的幾種來說明

1 . 第一種方案

通過okhttp提供的Authenticator接口,但是檢視okhttp的源碼會發現,隻有傳回HTTP的狀态碼為401時,才會使用Authenticator接口,如果服務端設計規範,可以嘗試如下方法。

實作Authenticator接口

==注意== : 這裡要清楚401指的是HTTP狀态碼

public class TokenAuthenticator implements Authenticator {

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {

        //取出本地的refreshToken,如果該Token過期的話就要重新登入了
        String refreshToken = "XXXXXXXXXXXXXXXXXXX";

        // 通過一個特定的接口擷取新的token,此處要用到同步的retrofit請求
         HttpHelper httpHelper=  new HttpHelper(application);
        ApiService service = httpHelper.getApi(ApiService.class);
        Call<String> call = service.refreshToken(refreshToken);

        //使用retrofit的同步方式
        String newToken = call.execute().body();

        return response.request().newBuilder()
                .header("token", newToken)
                .build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        return null;
    }
}
           

然後給添加給OkHttpClient,這樣就對Token的重新整理OkHttpClient就自動搞定了,重新整理完Token後okhttp也會再次發起請求

OkHttpClient client = new OkHttpClient();
client.setAuthenticator(new TokenAuthenticator());
           

2 . 第二種方案

但是如果伺服器不能傳回401**HTTP狀态碼**的話,那麼上面的方案就不能解決問題了

有些時候背景會把Token過期歸為業務異常,是以出現了伺服器傳回200成功的HTTP狀态碼,但是傳回了401的業務Code碼,比如:
           
{
    "data":{
        //什麼都沒有傳回
    },
    "message":"success"
    "code":  //這裡是業務Code代表Token過期了,沒有傳回任何資料,需要重新整理Token,每個伺服器定義的TokenCode值也是不一樣的

    }
           

通過okhttp的攔截器,okhttp 2.2.0 以後提供了攔截器的功能

public class TokenInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {

        Response response = chain.proceed(request);
        Request.Builder requestBuilder = original.newBuilder();
        String access_token = (String) SPHelper.get(AppContext.get(),"access_token","");
        if(!TextUtils.isEmpty(access_token)){
            requestBuilder.addHeader("Authentication", access_token);
        }

        if (isTokenExpired(response)) {//根據和服務端的約定判斷token過期
            //同步請求方式,擷取最新的Token
           access_token = getNewToken();
             requestBuilder.header("Authentication", access_token);
        }
          Request request = requestBuilder.build();
       return chain.proceed(request);
    }

    /**
     * 根據Response,判斷Token是否失效
     *
     * @param response
     * @return
     */
    private boolean isTokenExpired(Response response) {
        if (response.code() == ) {
            return true;
        }
        return false;
    }

    /**
     * 同步請求方式,擷取最新的Token
     *
     * @return
     */
    private String getNewToken() throws IOException {
        // 通過一個特定的接口擷取新的token,此處要用到同步的retrofit請求
        String refreshToken = "XXXXXXXXXXXXXXXXXXX";

         HttpHelper httpHelper=  new HttpHelper(application);
        ApiService service = httpHelper.getApi(ApiService.class);
        Call<String> call = service.refreshToken(refreshToken);
       //使用retrofit的同步方式
        String newToken = call.execute().body();
        SPHelper.put(AppContext.get(),"access_token",newToken)//存本地
        return newToken;
    }
}
           

3 . 當然還有其他的解決方式,隻要能在統一請求回來之後,需要再次請求重新整理Token時候,請求伺服器就行,重點是要抓住這個時機,比如在Gson解析的時候修改GsonConverterFactory,判斷業務Code再重新整理Token,或者是在借助RxJava的flatMap實作都是同樣的原來.這些方法不同的就是這個時機點可以是:okhttp攔截器 ,也可以是Gson解析的時候,也可以在Gson解析後的配合Rxjava的flatMap是一樣的

  • 對應的加密資料
public class StringConverterFactory<T> implements Converter<ResponseBody, Object> {
    private final TypeAdapter<T> adapter;
    StringConverterFactory(TypeAdapter<T> adapter) {
        this.adapter = adapter;
    }

    @Override
    public Object convert(ResponseBody value) throws IOException {
        try {
            String aseKey= UserCache.getAesSecretKey();
            KLog.d("解密秘鑰aseKey = " + aseKey);
            String data = null;
            try {
                data = AES.dencrypt(value.string(), aseKey);
                KLog.e("傳回資料:"+data);
                BaseResponse baseResponse = new Gson().fromJson(data, BaseResponse.class);
                if(!StringUtils.isEmpty(baseResponse.getNewAesKey())){
                    //保證AESkey在緩存中
                    KLog.i("獲得新的key====="+baseResponse.getNewAesKey());
                    //緩存中擷取秘鑰
                    UserCache.saveAesSecretKey(baseResponse.getNewAesKey());
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        } finally {
            value.close();
        }
    }
}
           

==注意==:這裡也可以用來重新重新整理Token就看個人習慣和項目實際的需求吧

  • 失敗重連

雖然說在Okhttp基礎相關的設定中有涉及到說失敗重連的問題,但是不能滿足實際開發,接下來就使用Rxjava來進行改造,就線上程排程基礎上進行改造.如下:

/**
     * 統一線程處理
     *
     * @param <T>
     * @return
     */
    public static <T extends ApiResponse> FlowableTransformer<T, T> rxSchedulerHelper() {    //compose簡化線程
        return rxSchedulerHelper(,);
    }


   /**
     *  統一線程處理和失敗重連
     * @param count   失敗重連次數
     * @param delay  延遲時間
     * @param <T>      傳回資料data實際的 資料
     * @return   傳回資料data實際的 資料
     */
    public static <T extends ApiResponse> FlowableTransformer<T, T> rxSchedulerHelper(int count,long delay) {    //compose簡化線程
        return new FlowableTransformer<T, T>() {
            @Override
            public Flowable<T> apply(Flowable<T> upstream) {
                return upstream.subscribeOn(Schedulers.io())
                        .flatMap(new Function<T, Flowable<T>>() {
                            @Override
                            public Flowable<T> apply(T t) throws Exception {

                                //TODO 做些對傳回結果做一些操作
                                    return Flowable.just(t);

                            }
                        })
                        .retryWhen(new RetryWhenHandler(count, delay))
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }






public class RetryWhenHandler implements Function<Flowable<? extends Throwable>, Flowable<?>> {

    private int mCount = ;
    private long mDelay = ; //s

    private int counter = ;

    public RetryWhenHandler() {
    }

    public RetryWhenHandler(int count) {
        this.mCount = count;
    }

    public RetryWhenHandler(int count, long delay) {
        this(count);
        this.mCount = count;
        this.mDelay = delay;
    }


    @Override
    public Flowable<?> apply(Flowable<? extends Throwable> flowable) throws Exception {
        return flowable
                .flatMap(new Function<Throwable, Flowable<?>>() {
                    @Override
                    public Flowable<?> apply(Throwable throwable) throws Exception {
                        if (counter < mCount && (throwable instanceof UnknownHostException
                                || throwable instanceof SocketException
                                || throwable instanceof HttpException
                        )) {
                            counter++;
                            return Flowable.timer(mDelay, TimeUnit.SECONDS);
                        } else if ((counter < mCount && throwable instanceof NullPointerException
                                && throwable.getMessage() != null) {
                            counter++;
                            return Flowable.timer(, TimeUnit.SECONDS);
                        }
                        return Flowable.error(throwable);
                    }

                });
    }
}
           

==說明== : 這裡的話是利用Rxjava中的retryWhen()方法來實作,當發現抛出的異常是屬于網絡異常就進行再次請求,這樣對重連次數以及時長的控制(這裡預設的話是重連三次,并且每次重連事件為3s,這裡可以根據具體的情況拟定),如果還麼有了解Rxjava的童靴們趕緊學起來吧.

  • 多檔案上傳

伺服器提供了檔案系統該怎麼上傳多檔案

有兩種方式

- 使用List

/**
     * 将檔案路徑數組封裝為{@link List<MultipartBody.Part>}
     * @param key 對應請求正文中name的值。目前伺服器給出的接口中,所有圖檔檔案使用<br>
     * 同一個name值,實際情況中有可能需要多個
     * @param filePaths 檔案路徑數組
     * @param imageType 檔案類型
     */
    public static List<MultipartBody.Part> files2Parts(String key,
                                                       String[] filePaths, MediaType imageType) {
        List<MultipartBody.Part> parts = new ArrayList<>(filePaths.length);
        for (int i = ; i <filePaths.length ; i++) {
            File file = new File(filePaths[i]);
            // 根據類型及File對象建立RequestBody(okhttp的類)
            RequestBody requestBody = RequestBody.create(imageType, file);
            // 将RequestBody封裝成MultipartBody.Part類型(同樣是okhttp的)
            MultipartBody.Part part = MultipartBody.Part.
                    createFormData(key+i, file.getName(), requestBody);

            // 添加進集合
            parts.add(part);
        }
        return parts;
    }

    /**
     * 其實也是将File封裝成RequestBody,然後再封裝成Part,<br>
     * 不同的是使用MultipartBody.Builder來建構MultipartBody
     * @param key 同上
     * @param filePaths 同上
     * @param imageType 同上
     */
    public static MultipartBody filesToMultipartBody(String key,
                                                     String[] filePaths,
                                                     MediaType imageType) {
        MultipartBody.Builder builder = new MultipartBody.Builder();

        for (int i = ; i <filePaths.length ; i++) {
            File file = new File(filePaths[i]);
            RequestBody requestBody = RequestBody.create(imageType, file);
            builder.addFormDataPart(key+i, file.getName(), requestBody);
        }

        builder.setType(MultipartBody.FORM);
        return builder.build();
    }
           

然後使用Retrofit定義好Api

public interface FileUploadApi {


 String baseURL= Constants.MOCK_FILE_UPLOAD;

 /**
  * 注意1:必須使用{@code @POST}注解為post請求<br>
  * 注意:使用{@code @Multipart}注解方法,必須使用{@code @Part}/<br>
  * {@code @PartMap}注解其參數<br>
  * 本接口中将文本資料和檔案資料分為了兩個參數,是為了友善将封裝<br>
  * {@link MultipartBody.Part}的代碼抽取到工具類中<br>
  * 也可以合并成一個{@code @Part}參數
  * @param params 用于封裝文本資料
  * @param parts 用于封裝檔案資料
  * @return BaseResp為伺服器傳回的基本Json資料的Model類
  */
 @Multipart
 @POST()
 Flowable<ApiResponse<UploadBackBean>> requestUploadWork(@Url String url,@PartMap Map<String, RequestBody> params,
                                                         @Part() List<MultipartBody.Part> parts);

 /**
  * 注意1:必須使用{@code @POST}注解為post請求<br>
  * 注意2:使用{@code @Body}注解參數,則不能使用{@code @Multipart}注解方法了<br>
  * 直接将所有的{@link MultipartBody.Part}合并到一個{@link MultipartBody}中
  */
 @POST()
 Flowable<ApiResponse<UploadBackBean>> requestUploadWork(@Url String url,@Body MultipartBody body);

}
           

最後使用;

public  void  requestUploadWork(String[] files) {
  List<MultipartBody.Part> parts = UploadUtil.files2Parts("file", files, MediaType.parse("multipart/form-data"));
    return mHttpHelper.getApi(FileUploadApi.class).requestUploadWork(Constants.BASE_URL+"file/upload/img",parts).compose(RxUtil.<ApiResponse<String>>rxSchedulerHelper(0, 5))
                .subscribe(new BaseSubscriber<ApiResponse<String>>(new KrSubscriberListener<String>() {
                    @Override
                    public void onSuccess(String response) {
                       //todo
                    }

                    @Override
                    public void onFail(String errorCode, String errorMsg) {
                        super.onFail(errorCode, errorMsg);
                      //todo
                    }
                }));
}



public  void  requestUploadWork(String[] files) {
 MultipartBody parts = UploadUtil.filesToMultipartBody("file", files, MediaType.parse("multipart/form-data"));
    return mHttpHelper.getApi(FileUploadApi.class).requestUploadWork(Constants.BASE_URL+"file/upload/img",parts).requestUploadWork(Constants.BASE_URL+"file/upload/img",parts).compose(RxUtil.<ApiResponse<String>>rxSchedulerHelper(0, 5))
                .subscribe(new BaseSubscriber<ApiResponse<String>>(new KrSubscriberListener<String>() {
                    @Override
                    public void onSuccess(String response) {
                       //todo
                    }

                    @Override
                    public void onFail(String errorCode, String errorMsg) {
                        super.onFail(errorCode, errorMsg);
                      //todo
                    }
                }));;
}
           

這樣就實作了多檔案的上傳

最後

對于前面的一些正常設定也提供了拓展

  • HttpHelper

項目位址:

Flyabbit https://github.com/chengzichen/Flyabbit

該項目也會随着文章的更新而更新敬請關注

關于我

  • Github:https://github.com/chengzichen
  • CSDN : http://blog.csdn.net/chengzichen_
  • 個人部落格 : https://chengzichen.github.io/

最後感謝 : 代碼家 ,以及一些大佬的幫助

參考 :https://github.com/square/okhttp/wiki/Recipes#handling-authentication