1. 程式人生 > >[從零開始系列]AndroidApp研發之路(一) 網路請求的封裝(一)

[從零開始系列]AndroidApp研發之路(一) 網路請求的封裝(一)

文章目錄:
- 前言
- 封裝成果
- 封裝細節
- 如何使用
- 注意
- 作者

前言

寫在前面

  • 技術選型
  • 元件化設計
  • ReactNative-Android 的簡單實踐
  • 阿里Atlas(外掛化)與該專案的簡單實踐

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

Image text

初衷:

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

網路請求

為什麼要封裝適合自己公司的網路請求框架

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

比如:被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的實現類就行了不用修改其他的程式碼,在通過注入的方式注入到各個模組中去.如下圖:

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

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, 1024 * 1024 * 40);//設定快取大小為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(15, TimeUnit.SECONDS)//連線超時
                        .writeTimeout(3, TimeUnit.SECONDS)//寫超時超時
                        .readTimeout(3, 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
com.github.franmontiel:PersistentCookieJar:v1.0.0

但是沒聽見但是嗎? 實際上伺服器安全認證的方式多種多樣,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:200 ,
    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":401  //這裡是業務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() == 401) {
            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(3,5);
    }


   /**
     *  統一執行緒處理和失敗重連
     * @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 = 3;
    private long mDelay = 3; //s

    private int counter = 0;

    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(0, 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 = 0; 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 = 0; 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
                    }
                }));;
}

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

最後

對於前面的一些常規設定也提供了拓展

該專案也會隨著文章的更新而更新敬請關注

關於我

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