Retorfit +okhttp 簡單的封裝 用作公共請求底層實現

網上有很多類似的封裝實例,比這個好的也有很多,爲什麼還要在寫一下呢? 

主要是因爲在經歷了幾個別人的項目架構後發現,很嚴重的業務耦合全部都放到網絡框架上了,所以覺得 還是有必要記錄一下, 大神勿噴!!!

本文主要說的是 post get 請求封裝的一些東西 下面直接上代碼


需要引入的包
   api 'com.squareup.okhttp3:okhttp:3.10.0'
    api 'com.squareup.okio:okio:1.11.0'
    api 'com.squareup.okhttp3:logging-interceptor:4.0.1'
//    retorfit
    api 'com.squareup.retrofit2:retrofit:2.2.0'
    api 'com.squareup.retrofit2:adapter-rxjava2:2.6.0'
    api 'com.squareup.retrofit2:converter-gson:2.6.0'
    api 'com.google.code.gson:gson:2.8.2'
    /*rx-android-java*/
    api 'io.reactivex.rxjava2:rxjava:2.2.10'
    api 'io.reactivex.rxjava2:rxandroid:2.1.1'
    api 'com.squareup.retrofit2:converter-scalars:2.6.0'
    api 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'

 

首先定義一個RetorfitManager管理類 

package com.wmy.lib.mvp.http;

import com.google.gson.Gson;
import com.wmy.lib.mvp.base.BaseApplication;
import com.wmy.lib.mvp.common.Constant;
import com.wmy.lib.mvp.http.listener.DownloadCallBack;
import com.wmy.lib.mvp.http.listener.FileDownLoadObserver;
import com.wmy.lib.mvp.http.listener.ProgressListener;
import com.wmy.lib.mvp.http.listener.ResponseCallBack;
import com.wmy.lib.mvp.http.okhttp.ProgressRequestBody;
import com.wmy.lib.mvp.http.okhttp.ProgressResponseBody;
import com.wmy.lib.mvp.utils.FileUtils;
import com.wmy.lib.mvp.utils.LogUtils;
import com.wmy.lib.mvp.utils.NetworkUtils;
import com.wmy.lib.mvp.utils.SPDownloadUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.ConnectionPool;

import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;

import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;

import retrofit2.Retrofit;

import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;

import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;

/**
 * retrofit 管理類
 * 使用單利模式 實現( 後期可能會調整 使用構造者 如果有此需求的話)
 * 所有請求底層都需要走這個類實現
 * 本類主要封裝 各種請求方式 get post 等、、 根據業務需求可增加調整
 * 封裝網絡 可 增加請求攔截 和返回攔截處理
 * 回調使用rxjava 轉換observer 使用gson 通過泛型類型轉會 業務層需要的bean
 *
 * @author:wmyas
 * @date:2019/7/24 19:21
 */
public class RetrofitManager<T> {

    private static final int CONNEC_TIMEOUT = 5;
    private static final int READ_TIMEOUT = 10;

    private static ApiService apiServer;
    private static RetrofitManager mInstance;

    private static OkHttpClient mOkhttp;
    private static Retrofit mRetrofit;
    private boolean isCach;



    //構造retrofit 管理
    private RetrofitManager() {
        mRetrofit = new Retrofit.Builder()
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())

                .addConverterFactory(ScalarsConverterFactory.create())
                .baseUrl("https://easydoc.xyz/mock/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(getOkhttpClient())
                .build();
        apiServer = mRetrofit.create(ApiService.class);
    }

    //定義okhttp 客戶端
    private OkHttpClient getOkhttpClient() {
        //啓用Log日誌
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        mOkhttp = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .connectTimeout(CONNEC_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS)

                .retryOnConnectionFailure(false)
                .connectionPool(new ConnectionPool())
                .build();

        return mOkhttp;
    }

    /**
     * 是否添加緩存
     * @param isCach
     */
    public void isCach(boolean isCach){
        this.isCach=isCach;
    }
    private OkHttpClient.Builder setCach() {
        Interceptor cacheIntercepter = new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                //對request的設置用來指定有網/無網下所走的方式
                //對response的設置用來指定有網/無網下的緩存時長

                Request request = chain.request();
                if (!NetworkUtils.isConnected()) {
                    //無網絡下強制使用緩存,無論緩存是否過期,此時該請求實際上不會被髮送出去。
                    //有網絡時則根據緩存時長來決定是否發出請求
                    request = request.newBuilder()
                            .cacheControl(CacheControl.FORCE_CACHE).build();
                }

                okhttp3.Response response = chain.proceed(request);
                if (NetworkUtils.isConnected()) {
                    //有網絡情況下,超過1分鐘,則重新請求,否則直接使用緩存數據
                    int maxAge = 60; //緩存一分鐘
                    String cacheControl = "public,max-age=" + maxAge;
                    //當然如果你想在有網絡的情況下都直接走網絡,那麼只需要
                    //將其超時時間maxAge設爲0即可
                    return response.newBuilder()
                            .header("Cache-Control", cacheControl)
                            .removeHeader("Pragma").build();
                } else {
                    //無網絡時直接取緩存數據,該緩存數據保存1周
                    int maxStale = 60 * 60 * 24 * 7 * 1;  //1周
                    return response.newBuilder()
                            .header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale)
                            .removeHeader("Pragma").build();
                }

            }
        };

        File cacheFile = new File(BaseApplication.getIns().getExternalCacheDir(), "HttpCache");//緩存地址
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 50); //大小50Mb

//設置緩存方式、時長、地址
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
        okHttpClientBuilder.addNetworkInterceptor(cacheIntercepter);
        okHttpClientBuilder.addInterceptor(cacheIntercepter);
        okHttpClientBuilder.cache(cache);
        return okHttpClientBuilder;
    }

    //通過單利模式初始化
    public static RetrofitManager getInstance() {
        if (mInstance == null) {
            synchronized (RetrofitManager.class) {
                if (mInstance == null) {
                    mInstance = new RetrofitManager();
                }
            }
        }
        return mInstance;
    }

    public ApiService getApiServer() {
        return apiServer;
    }


    /**
     * 公用的get 請求
     *
     * @param url              請求地址
     * @param maps             請求參數
     * @param responseCallBack 回調監聽
     */
    public void get(String url, Map<String, String> maps, ResponseCallBack responseCallBack) {
        Map map = maps;
        if (map == null) {
            map = new HashMap();
        }
        getApiServer().get(url, map)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getObserver(responseCallBack));

    }


    /**
     * 公用的post 請求
     *
     * @param url              請求地址
     * @param maps             請求參數
     * @param responseCallBack 回調監聽
     */
    public void post(String url, Map<String, String> maps, ResponseCallBack responseCallBack) {
        Map map = maps;
        if (map == null) {
            map = new HashMap();
        }
        getApiServer().post(url, map)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getObserver(responseCallBack));
    }


  
    /**
     * 單文件上傳
     *
     * @param url              請求地址
     * @param filepath         文件地址
     * @param responseCallBack 返回回調
     */
    public void uploadFile(String url, String filepath, ProgressListener responseCallBack) {

        File file = new File(filepath);
        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part filePart = MultipartBody.Part.createFormData("headimg", file.getName(), new ProgressRequestBody(requestFile, new ProgressRequestBody.Listener() {
            @Override
            public void onProgress(long bytesWritten, long contentLength) {
                responseCallBack.onProgress(bytesWritten,contentLength);
            }
        }));


        getApiServer().uploadFile(url, filePart)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getObserver(responseCallBack));
    }

    /**
     * 多文件上傳
     *
     * @param url
     * @param map
     * @param responseCallBack
     */
    public void uploadFiles(String url, Map<String, String> map, ProgressListener responseCallBack) {
        MultipartBody.Part[] parts = new MultipartBody.Part[map.size()];
        int cnt = 0;
        for (String key : map.keySet()) {
            System.out.println("key= " + key + " and value= " + map.get(key));
            //創建文件
            File file = new File(map.get(key));
            RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
            MultipartBody.Part filePart = MultipartBody.Part.createFormData("headimg[]", file.getName(), new ProgressRequestBody(requestFile, new ProgressRequestBody.Listener() {
                @Override
                public void onProgress(long bytesWritten, long contentLength) {
                    responseCallBack.onProgress(bytesWritten,contentLength);
                }
            }));
            parts[cnt] = filePart;
            cnt++;
        }

        getApiServer().uploadFile(url, parts, map).subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())

                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getObserver(responseCallBack));
    }

    /**
     * 文件下載
     * 使用前請確保有文件讀取權限
     *
     * @param url      請求地址
     * @param filePath 文件地址
     * @param fileName 文件名成
     * @param listener 下載監聽
     */
    public void downloadFile(String url, String filePath, String fileName, FileDownLoadObserver listener) {
        if (listener == null) {

        }
        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new Interceptor() {
                    @Override
                    public okhttp3.Response intercept(Chain chain) throws IOException {
                        okhttp3.Response response = chain.proceed(chain.request());
                        return response.newBuilder().body(new ProgressResponseBody(response.body(),
                                new ProgressResponseBody.ProgressListener() {
                                    @Override
                                    public void onProgress(long totalSize, long downSize) {
                                        onProgress(totalSize, downSize);
                                    }
                                })).build();
                    }
                }).build();

        Retrofit retrofit = new Retrofit.Builder().client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://wawa-api.vchangyi.com/").build();

        ApiService apiServer = retrofit.create(ApiService.class);

        apiServer.downloadFile(url)
                .map(new Function<ResponseBody, String>() {
                    @Override
                    public String apply(ResponseBody body) throws Exception {
                        File file = FileUtils.saveFile(body ,filePath, fileName);
                        return file.getPath();
                    }
                }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(listener);


//        getApiServer().downloadFile(url)
//                .subscribeOn(Schedulers.io())//subscribeOn和ObserOn必須在io線程,如果在主線程會出錯
//                .observeOn(Schedulers.io())
//                .observeOn(Schedulers.computation())//需要
//                .map(new Function<ResponseBody, File>() {
//                    @Override
//                    public File apply(@NonNull ResponseBody responseBody) throws Exception {
//                        return listener.writeResponseBodyToDisk(responseBody, filePath, fileName);
//                    }
//                })
//                .observeOn(AndroidSchedulers.mainThread())
//                .subscribe(listener);
    }


    public void downloadFile(final long range, final String url, final String fileName, final DownloadCallBack downloadCallback) {
        //斷點續傳時請求的總長度
        File file = new File(Constant.APP_ROOT_PATH + Constant.DOWNLOAD_DIR, fileName);
        String totalLength = "-";
        if (file.exists()) {
            totalLength += file.length();
        }

        getApiServer().downloadFile("bytes=" + Long.toString(range) + totalLength, url)
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(ResponseBody responseBody) {
                        RandomAccessFile randomAccessFile = null;
                        InputStream inputStream = null;
                        long total = range;
                        long responseLength = 0;
                        try {
                            byte[] buf = new byte[2048];
                            int len = 0;
                            responseLength = responseBody.contentLength();
                            inputStream = responseBody.byteStream();
                            String filePath = Constant.APP_ROOT_PATH + Constant.DOWNLOAD_DIR;
                            File file = new File(filePath, fileName);
                            File dir = new File(filePath);
                            if (!dir.exists()) {
                                dir.mkdirs();
                            }
                            randomAccessFile = new RandomAccessFile(file, "rwd");
                            if (range == 0) {
                                randomAccessFile.setLength(responseLength);
                            }
                            randomAccessFile.seek(range);

                            int progress = 0;
                            int lastProgress = 0;

                            while ((len = inputStream.read(buf)) != -1) {
                                randomAccessFile.write(buf, 0, len);
                                total += len;
                                lastProgress = progress;
                                progress = (int) (total * 100 / randomAccessFile.length());
                                if (progress > 0 && progress != lastProgress) {
                                    downloadCallback.onProgress(progress);
                                }
                            }
                            downloadCallback.onCompleted();
                        } catch (Exception e) {
//                            Log.d(TAG, e.getMessage());
                            downloadCallback.onError(e.getMessage());
                            e.printStackTrace();
                        } finally {
                            try {
                                SPDownloadUtil.getInstance().save(url, total);
                                if (randomAccessFile != null) {
                                    randomAccessFile.close();
                                }

                                if (inputStream != null) {
                                    inputStream.close();
                                }

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

                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        downloadCallback.onError(e.toString());
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }
    /**
     * 上傳/下載文件 訂閱監聽
     *
     * @param listener
     * @return
     */
    private Observer getObserver(final ProgressListener listener) {
        Observer observer = new Observer<ResponseBody>() {
            @Override
            public void onError(Throwable e) {
                LogUtils.d("onError");
                e.printStackTrace();
                if (listener != null) {
                    listener.onUpLoadFail(e);
                }
            }

            /**
             * Notifies the Observer that the {@link Observable} has finished sending push-based notifications.
             * <p>
             * The {@link Observable} will not call this method if it calls {@link #onError}.
             */
            @Override
            public void onComplete() {

            }

            /**
             * Provides the Observer with the means of cancelling (disposing) the
             * connection (channel) with the Observable in both
             * synchronous (from within {@link #onNext(ResponseBody)}) and asynchronous manner.
             *
             * @param d the Disposable instance whose {@link Disposable#dispose()} can
             *          be called anytime to cancel the connection
             * @since 2.0
             */
            @Override
            public void onSubscribe(Disposable d) {
                LogUtils.d("onSubscribe");
            }

            /**
             * Provides the Observer with a new item to observe.
             * <p>
             * The {@link Observable} may call this method 0 or more times.
             * <p>
             * The {@code Observable} will not call this method again after it calls either {@link #onComplete} or
             * {@link #onError}.
             *
             * @param response the item emitted by the Observable
             */
            @Override
            public void onNext(ResponseBody response) {
                LogUtils.d("onNext :-----------");
                //處理監聽爲空的邏輯
                if (listener == null) return;

                listener.onUpLoadSuccess(response);
                System.out.println("--------------------------");
            }

        };
        return observer;
    }

    /**
     * 正常請求 訂閱監聽及攔截
     *
     * @param listener
     * @return
     */
    private Observer getObserver(final ResponseCallBack listener) {
        Observer observer = new Observer<Response>() {
            @Override
            public void onError(Throwable e) {
                LogUtils.d("onError");
                e.printStackTrace();
                if (listener != null) {
                    listener.onError(e);
                }
            }

            /**
             * Notifies the Observer that the {@link Observable} has finished sending push-based notifications.
             * <p>
             * The {@link Observable} will not call this method if it calls {@link #onError}.
             */
            @Override
            public void onComplete() {

            }

            /**
             * Provides the Observer with the means of cancelling (disposing) the
             * connection (channel) with the Observable in both
             * synchronous (from within {@link #onNext(Response)}) and asynchronous manner.
             *
             * @param d the Disposable instance whose {@link Disposable#dispose()} can
             *          be called anytime to cancel the connection
             * @since 2.0
             */
            @Override
            public void onSubscribe(Disposable d) {
                LogUtils.d("onSubscribe");
            }

            /**
             * Provides the Observer with a new item to observe.
             * <p>
             * The {@link Observable} may call this method 0 or more times.
             * <p>
             * The {@code Observable} will not call this method again after it calls either {@link #onComplete} or
             * {@link #onError}.
             *
             * @param response the item emitted by the Observable
             */
            @Override
            public void onNext(Response response) {
                LogUtils.d("onNext :-----------");
                //處理監聽爲空的邏輯
                if (listener == null) return;
                if (response.code != 200) {
                    onError(new Throwable("業務錯誤"));
                    return;
                }
                //這裏獲得到的是類的泛型的類型
                LogUtils.d("onNext rawType length :" + listener.getClass().getGenericInterfaces().length);
                LogUtils.d("onNext rawType :" + listener.getClass().getGenericInterfaces()[0]);


                try {
                    converJson(response, listener);
                } catch (Exception e) {
                    onError(new Throwable("轉換異常 :", e));
                }
                System.out.println("--------------------------");
            }

        };
        return observer;
    }
/**
 * 轉換返回對象 根據泛型類型處理轉換
 * @param response
 * @param listener
 */   
 private void converJson(Response response, ResponseCallBack listener) {
        String myJson="";
        if(response.data==null){
            myJson=   new Gson().toJson(response.result);
        }else
            myJson=   new Gson().toJson(response.data);
        if (listener.getClass().getGenericInterfaces().length > 0) {
            Type type = listener.getClass().getGenericInterfaces()[0];
            // 泛型的實際類型
            Type typeArgument = ((ParameterizedType) type).getActualTypeArguments()[0]; // 泛型的參數
            LogUtils.d("onNext typeArgument :" + typeArgument);


            listener.onSuccess(new Gson().fromJson(myJson, typeArgument));

        } else {
            listener.onSuccess(myJson);
        }
    }
}

數據回調接口 ResponseCallBack ,上傳下載的 接口實現類似,這裏就不貼了

package com.wmy.lib.mvp.http.listener;



//通過泛型類型對數據進行轉換
public interface ResponseCallBack<T> {
    /**
     * 加載數據成功
     *
     * @param response 返回的數據
     */
    void onSuccess(T response);
 
    /**
     * 加載數據失敗
     *
     * @param throwable 錯誤信息
     */
    void onError(Throwable throwable);
}

 

返回消息體解析 Response 這裏說明一下,本人寫的demo 由於接入不同接口測試 返回結構有些許的區別,故接受比較多一些,

標準  一般 msg ,data ,code 之類的就可以

package com.wmy.lib.mvp.http;

import java.io.Serializable;

/**
 * 標準數據格式
 * @author:wmyas
 * @date:2019/7/25 11:07
 */
public class Response<T> implements Serializable {
    public String msg;
    public String message;
    public T data;
    public T result;
    public int code;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getResult() {
        return result;
    }

    public void setResult(T result) {
        this.result = result;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return "Response{" +
                "msg='" + msg + '\'' +
                ", data=" + data +
                ", code=" + code +
                '}';
    }
}

ApiService 

package com.wmy.lib.mvp.http;

import java.util.Map;

import io.reactivex.Observable;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
import retrofit2.http.QueryMap;
import retrofit2.http.Streaming;
import retrofit2.http.Url;


/**
 * 公共接口定義
 *
 * @author:wmyas
 * @date:2019/7/24 17:36
 */
public interface ApiService {

    @GET
    Observable<Response> get(@Url String url, @QueryMap Map<String, String> maps);

    @FormUrlEncoded
    @POST
    Observable<Response> post(@Url String url, @FieldMap Map<String, String> maps);

    @POST()
    Observable<Response> postBody(@Url String url, @QueryMap Map<String, String> urlMaps, @Body RequestBody body);
    @POST
    @Multipart
    Observable<ResponseBody> uploadFile(@Url String url, @Part MultipartBody.Part file);

    @Multipart
    @POST
    Observable<ResponseBody> uploadFile(@Url String url, @Part MultipartBody.Part[] parts, @FieldMap Map<String, String> maps);

    @Streaming
    @GET
    Observable<ResponseBody> downloadFile(@Url String fileUrl);

    /**
     * 支持斷點續傳
     * @param range 總長度
     * @param url
     * @return
     */
    @Streaming
    @GET
    Observable<ResponseBody> downloadFile(@Header("Range") String range, @Url() String url);
}

 

需要注意的是該網絡框架的簡單封裝,是爲了弱化業務耦合,也是爲了開發時不用處理json 解析和 bean 之間來回轉換

實際調用通過請求回調 傳入的泛型,解析攔截, 開發時候 不會關心 外層的 msg ,code。 只需要關心data返回的bean 然後回調到ui層

RetrofitManager.getInstance().get("https://www.apiopen.top/novelApi", null, new ResponseCallBack<List<NovelBean>>() {
    /**
     * 加載數據成功
     *
     * @param response 返回的數據
     */
    @Override
    public void onSuccess(List<NovelBean> response) {
        getView().showRecommendNovel(response);
    }

    @Override
    public void onError(Throwable throwable) {

    }
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章