Android網(wǎng)絡(luò)篇(四)—— 自己動(dòng)手封裝一個(gè)屬于自己的網(wǎng)絡(luò)請(qǐng)求框架

網(wǎng)絡(luò)請(qǐng)求框架算是android體系當(dāng)中一個(gè)比較重要的部分柔昼,在android歷史中關(guān)于網(wǎng)絡(luò)的演進(jìn)也經(jīng)歷了幾個(gè)階段病往,到目前為止,比較通用的網(wǎng)絡(luò)請(qǐng)求框架就是OkHttp + Retrofit +RxJava+Gson,當(dāng)然,關(guān)于這個(gè)組合使用網(wǎng)上也有很多句喜,但是,那個(gè)畢竟是別人的東西沟于,大部分時(shí)候只有適合自己的才是最好的咳胃,所以,自己封裝一個(gè)網(wǎng)絡(luò)請(qǐng)求框架就顯得比較重要了旷太,廢話不多說(shuō)展懈,直接動(dòng)手開(kāi)干。

網(wǎng)絡(luò)請(qǐng)求框架封裝大概流程如下:

(1)封裝OkHttp實(shí)例
(2)封裝OkHttp的攔截請(qǐng)求
(3)封裝Retrofit實(shí)例
(4)封裝Retrofit的注解通用類
(5)增加請(qǐng)求失敗重試功能
(6)封裝返回結(jié)果
(7)對(duì)上述封裝進(jìn)行組裝形成一個(gè)完整的框架

第一步:封裝OkHttp實(shí)例

public class OkHttpClientUtil {

    // 是否開(kāi)啟攔截供璧,默認(rèn)情況下開(kāi)啟
    private boolean mIsIntercept = true;
    // 設(shè)置數(shù)據(jù)讀取超時(shí)時(shí)間
    private long mReadTimeOut = 20000;
    // 設(shè)置網(wǎng)絡(luò)連接超時(shí)時(shí)間
    private long mConnectTimeOut = 20000;
    // 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
    private long mWriteTimeOut = 20000;

    private static volatile OkHttpClientUtil okHttpUtil;

    static OkHttpClientUtil getOkHttpUtil() {
        if (okHttpUtil == null) {
            synchronized (OkHttpClientUtil.class) {
                if (okHttpUtil == null) {
                    okHttpUtil = new OkHttpClientUtil();
                }
            }
        }
        return okHttpUtil;
    }

    /**
     * 私有構(gòu)造函數(shù)存崖,保證全局唯一
     */
    private OkHttpClientUtil(){

    }

    // 設(shè)置數(shù)據(jù)讀取超時(shí)時(shí)間
    OkHttpClientUtil setTimeOutTime(long timeout) {
        mReadTimeOut = timeout;
        return this;
    }

    // 設(shè)置網(wǎng)絡(luò)連接超時(shí)時(shí)間
    OkHttpClientUtil setConnectTime(long timeout) {
        mConnectTimeOut = timeout;
        return this;
    }

    // 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
    OkHttpClientUtil setWriteTime(long timeout) {
        mWriteTimeOut = timeout;
        return this;
    }

    // 設(shè)置攔截器
    OkHttpClientUtil setIntercept(boolean isIntercept) {
        this.mIsIntercept = isIntercept;
        return this;
    }

    // 設(shè)置Build方法
    public OkHttpClient build() {
        OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
        okHttpClient.readTimeout(mReadTimeOut, TimeUnit.MILLISECONDS);
        okHttpClient.connectTimeout(mConnectTimeOut, TimeUnit.MILLISECONDS);
        okHttpClient.writeTimeout(mWriteTimeOut, TimeUnit.MILLISECONDS);
        // 默認(rèn)開(kāi)啟請(qǐng)求的打印信息數(shù)據(jù),在每次發(fā)布版本的時(shí)候可以手動(dòng)關(guān)閉
        if (mIsIntercept) {
            okHttpClient.addInterceptor(new HttpRequestInterceptor());
        }
        return okHttpClient.build();
    }
}

第二步:封裝OkHttp的攔截請(qǐng)求

/**
 * author: zhoufan
 * data: 2021/7/27 14:39
 * content: 攔截每次發(fā)出的請(qǐng)求并打印出來(lái)
 */
public class HttpRequestInterceptor implements Interceptor {

    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request request = chain.request();
        long startTime = System.currentTimeMillis();
        okhttp3.Response response = chain.proceed(chain.request());
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        okhttp3.MediaType mediaType = response.body().contentType();
        String content = response.body().string();
        Log.d("request", "請(qǐng)求地址:| " + request.toString());
        if (request.body() != null) {
            printParams(request.body());
        }
        Log.d("request", "請(qǐng)求體返回:| Response:" + content);
        Log.d("request", "----------請(qǐng)求耗時(shí):" + duration + "毫秒----------");
        return response.newBuilder().body(okhttp3.ResponseBody.create(mediaType, content)).build();
    }

    private void printParams(RequestBody body) {
        Buffer buffer = new Buffer();
        try {
            body.writeTo(buffer);
            Charset charset = Charset.forName("UTF-8");
            MediaType contentType = body.contentType();
            if (contentType != null) {
                charset = contentType.charset(UTF_8);
            }
            String params = buffer.readString(charset);
            Log.d("request", "請(qǐng)求參數(shù): | " + params);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第三步:封裝Retrofit實(shí)例

/**
 * author: zhoufan
 * data: 2021/7/27 14:45
 * content: 對(duì)Retrofit進(jìn)行封裝
 */
public class RetrofitClientUtil {

    // 網(wǎng)絡(luò)請(qǐng)求的baseUrl
    private String mBaseUrl;
    // 設(shè)置數(shù)據(jù)解析器
    private Converter.Factory mBaseFactory;
    // 新增數(shù)據(jù)解析器
    private Converter.Factory mAddFactory;
    // 設(shè)置網(wǎng)絡(luò)請(qǐng)求適配器
    private CallAdapter.Factory mCallFactory;
    // 設(shè)置OkHttpClient
    private OkHttpClient mOkHttpClient;

    private static volatile RetrofitClientUtil retrofitUtil;

    public static RetrofitClientUtil getRetrofitUtil() {
        if (retrofitUtil == null) {
            synchronized (RetrofitClientUtil.class) {
                if (retrofitUtil == null) {
                    retrofitUtil = new RetrofitClientUtil();
                }
            }
        }
        return retrofitUtil;
    }

    private RetrofitClientUtil() {
        mBaseUrl = HttpRequestConstants.BASE_URL;
        // 默認(rèn)基礎(chǔ)數(shù)據(jù)類型的解析
        mBaseFactory = ScalarsConverterFactory.create();
        // RxJava來(lái)處理Call返回值
        mCallFactory = RxJava3CallAdapterFactory.create();
    }

    // 設(shè)置BaseUrl
    public RetrofitClientUtil setBaseUrl(String baseUrl) {
        this.mBaseUrl = baseUrl;
        return this;
    }

    // 設(shè)置數(shù)據(jù)解析
    public RetrofitClientUtil addConverterFactory(Converter.Factory factory) {
        this.mAddFactory = factory;
        return this;
    }

    // 設(shè)置網(wǎng)絡(luò)請(qǐng)求適配器
    public RetrofitClientUtil addCallAdapterFactory(CallAdapter.Factory factory) {
        this.mCallFactory = factory;
        return this;
    }

    // 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
    public RetrofitClientUtil setOkHttpClient(OkHttpClient okHttpClient) {
        this.mOkHttpClient = okHttpClient;
        return this;
    }

    // 設(shè)置Build方法
    public Retrofit build() {
        Retrofit.Builder builder = new Retrofit.Builder();
        builder.baseUrl(mBaseUrl);
        builder.addConverterFactory(mBaseFactory);
        if (mAddFactory!=null){
            builder.addConverterFactory(mAddFactory);
        }
        builder.addCallAdapterFactory(mCallFactory);
        builder.client(mOkHttpClient);
        return builder.build();
    }
}

第四步:封裝Retrofit的注解通用類

public interface ApiService {

    // baseUrl = http://www.5mins-sun.com:8081/

    // 例如:http://www.baidu.com不使用baseUrl
    @GET
    Observable<String> getPath(@Url String url);

    // GET請(qǐng)求(無(wú)參) api = news
    // http://www.5mins-sun.com:8081/news
    @GET("{api}")
    Observable<String> getData(@Path("api") String api);

    // GET請(qǐng)求(帶參) api = news
    // http://www.5mins-sun.com:8081/news?name=admin&pwd=123456
    @GET("{api}")
    Observable<String> getData(@Path("api") String api, @QueryMap TreeMap<String, Object> map);

    // POST請(qǐng)求(無(wú)參)
    @POST("{api}")
    Observable<String> postData(@Path(value = "api", encoded = true) String api);

    // POST請(qǐng)求,以RequestBody方式提交  api = news
    // http://www.5mins-sun.com:8081/news
    // RequestBody:
    // {
    //    "albumID": 2,
    //    "sectionID": 16
    // }
    @POST("{api}")
    Observable<String> postData(@Path(value = "api", encoded = true) String api, @Body RequestBody requestBody);

    // Post請(qǐng)求,以表單方式提交
    // http://www.5mins-sun.com:8081/news
    // user=admin
    // pwd=123456
    @FormUrlEncoded
    @POST("{api}")
    Observable<ResponseBody> postData(@Path("api") String api, @FieldMap Map<String, String> maps);

    // Post請(qǐng)求(帶數(shù)組)
    @FormUrlEncoded
    @POST("{api}")
    Observable<ResponseBody> postData(@Path("api") String api, @FieldMap Map<String, String> maps, @Query("meta[]") String... linked);

    // 上傳單個(gè)文件
    @Multipart
    @POST("{api}/")
    Observable<String> upload(@Path("api") String api, @PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);

    // 上傳多個(gè)文件
    @Multipart
    @POST("{api}/")
    Observable<String> uploadMultipart(@Path("api") String api, @PartMap Map<String, RequestBody> maps, @Part List<MultipartBody.Part> params);

}

第五步:增加請(qǐng)求失敗重試功能

/**
 * author: zhoufan
 * data: 2021/7/29 11:39
 * content: 設(shè)置重新連接
 */
public class RetryFunction implements Function<Observable<Throwable>, ObservableSource<?>> {

    // 可重試次數(shù)
    private int maxConnectCount = 3;
    // 當(dāng)前已重試次數(shù)
    private int currentRetryCount = 0;
    // 重試等待時(shí)間
    private int waitRetryTime = 0;

    @Override
    public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Throwable {
        // 參數(shù)Observable<Throwable>中的泛型 = 上游操作符拋出的異常嗜傅,可通過(guò)該條件來(lái)判斷異常的類型
        return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {

                // 輸出異常信息
                LogUtil.d(HttpRequestConstants.TAG, "發(fā)生異常 = " + throwable.toString());

                /**
                 * 需求1:根據(jù)異常類型選擇是否重試
                 * 即金句,當(dāng)發(fā)生的異常 = 網(wǎng)絡(luò)異常 = IO異常 才選擇重試
                 */
                if (throwable instanceof IOException) {
                    Log.d(HttpRequestConstants.TAG, "屬于IO異常,需重試");

                    /**
                     * 需求2:限制重試次數(shù)
                     * 即吕嘀,當(dāng)已重試次數(shù) < 設(shè)置的重試次數(shù)违寞,才選擇重試
                     */
                    if (currentRetryCount < maxConnectCount) {

                        // 記錄重試次數(shù)
                        currentRetryCount++;
                        Log.d(HttpRequestConstants.TAG, "重試次數(shù) = " + currentRetryCount);

                        /**
                         * 需求2:實(shí)現(xiàn)重試
                         * 通過(guò)返回的Observable發(fā)送的事件 = Next事件,從而使得retryWhen()重訂閱偶房,最終實(shí)現(xiàn)重試功能
                         *
                         * 需求3:延遲1段時(shí)間再重試
                         * 采用delay操作符 = 延遲一段時(shí)間發(fā)送趁曼,以實(shí)現(xiàn)重試間隔設(shè)置
                         *
                         * 需求4:遇到的異常越多,時(shí)間越長(zhǎng)
                         * 在delay操作符的等待時(shí)間內(nèi)設(shè)置 = 每重試1次棕洋,增多延遲重試時(shí)間1s
                         */
                        // 設(shè)置等待時(shí)間
                        waitRetryTime = 1000 + currentRetryCount * 1000;
                        Log.d(HttpRequestConstants.TAG, "等待時(shí)間 =" + waitRetryTime);
                        return Observable.just(1).delay(waitRetryTime, TimeUnit.MILLISECONDS);
                    } else {
                        // 若重試次數(shù)已 > 設(shè)置重試次數(shù)挡闰,則不重試
                        // 通過(guò)發(fā)送error來(lái)停止重試(可在觀察者的onError()中獲取信息)
                        return Observable.error(new Throwable("重試次數(shù)已超過(guò)設(shè)置次數(shù) = " + currentRetryCount + ",即 不再重試"));
                    }
                }
                // 若發(fā)生的異常不屬于I/O異常掰盘,則不重試
                // 通過(guò)返回的Observable發(fā)送的事件 = Error事件 實(shí)現(xiàn)(可在觀察者的onError()中獲取信息)
                else {
                    return Observable.error(new Throwable("發(fā)生了非網(wǎng)絡(luò)異常(非I/O異常)"));
                }
            }
        });
    }
}

第六步:封裝返回結(jié)果

/**
 * author: zhoufan
 * data: 2021/7/27 16:59
 * content: 處理接口返回的請(qǐng)求結(jié)果
 */
public class RxJavaObserver implements Observer<String> {

    private HttpRequestCallback mCallBack;
    private int mType;
    private Context mContext;

    RxJavaObserver(Context context, int type, HttpRequestCallback callBack) {
        this.mCallBack = callBack;
        this.mContext = context;
        this.mType = type;
    }

    @Override
    public void onSubscribe(@NonNull Disposable d) {

    }

    /**
     * 接口請(qǐng)求成功
     */
    @Override
    public void onNext(@NonNull String s) {
        try {
            if (HttpTool.isJsonObject(s)) {
                JSONObject jsonObject = new JSONObject(s);
                if (jsonObject.has("resultStatus")) {
                    String response = jsonObject.getString("resultStatus");
                    if (response.toUpperCase().equals("SUCCESS")) {
                        if (jsonObject.has("returnData")) {
                            mCallBack.onRequestSuccess(jsonObject.getString("returnData"), mType);
                        } else {
                            if (jsonObject.has("errCode") && jsonObject.has("errMsg")) {
                                String code = jsonObject.getString("errCode");
                                String errMsg = jsonObject.getString("errMsg");
                                mCallBack.onRequestSuccess(errMsg, mType);
                            }
                        }
                    } else {
                        String errMsg = jsonObject.getString("errMsg");
                        String code = jsonObject.getString("errCode");
                        mCallBack.onRequestFail(errMsg, code, mType);
                    }
                }
            } else {
                mCallBack.onRequestSuccess(s, mType);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * 接口請(qǐng)求失敗
     */
    @Override
    public void onError(@NonNull Throwable e) {
        // 1.檢查網(wǎng)絡(luò)設(shè)置
        if (!HttpTool.hasNetwork(mContext)) {
            MyToast.showCenterSortToast(mContext, mContext.getResources().getString(R.string.connect_error));
            onComplete();
            mCallBack.onRequestNetFail(mType);
            return;
        }
        // 2.非網(wǎng)絡(luò)錯(cuò)誤摄悯,接口請(qǐng)求錯(cuò)誤
        mCallBack.onRequestFail(e.getMessage(), "0000", mType);
    }

    /**
     * 接口請(qǐng)求完成
     */
    @Override
    public void onComplete() {

    }
}

第七步:對(duì)上述封裝進(jìn)行組裝形成一個(gè)完整的框架

/**
 * author: zhoufan
 * data: 2021/7/27 16:41
 * content: 對(duì)應(yīng)用層暴露的調(diào)用類
 */
public class HttpRetrofitRequest extends HttpRetrofit implements IHttpRequest {

    private ApiService apiService;
    // 是否添加通用參數(shù)
    private boolean mIsAddCommonParams;

    private volatile static HttpRetrofitRequest INSTANCES;


    public static HttpRetrofitRequest getInstances() {
        if (INSTANCES == null) {
            synchronized (HttpRetrofitRequest.class) {
                if (INSTANCES == null) {
                    INSTANCES = new HttpRetrofitRequest();
                }
            }
        }
        return INSTANCES;
    }

    private HttpRetrofitRequest() {
        apiService = getApiManager();
    }

    /**
     * 設(shè)置通用參數(shù)
     */
    public void isAddCommonParams(boolean isAddCommonParams) {
        this.mIsAddCommonParams = isAddCommonParams;
    }

    // Get請(qǐng)求(使用Path形式)
    @Override
    public void mHttpGetPath(Context context, String url, int type, HttpRequestCallback callBack) {
        Observable<String> observable = apiService.getPath(url);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }

    // GET請(qǐng)求(無(wú)參)
    @Override
    public void mHttpGet(Context context, String api, int type, HttpRequestCallback callBack) {
        Observable<String> observable = apiService.getData(api);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }

    // Get請(qǐng)求(帶參)
    @Override
    public void mHttpGet(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack) {
        if (mIsAddCommonParams) {
            HttpTool.getTreeCrc(map);
        }
        Observable<String> observable = apiService.getData(api, map);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }

    // Post請(qǐng)求(無(wú)參)
    @Override
    public void mHttpPost(Context context, String api, int type, HttpRequestCallback callBack) {
        Observable<String> observable = apiService.postData(api);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }

    // Post請(qǐng)求(帶參)
    // 以RequestBody方式提交
    @Override
    public void mHttpPost(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack) {
        if (mIsAddCommonParams) {
            HttpTool.getTreeCrc(map);
        }
        Gson gson = new Gson();
        String param = gson.toJson(map);
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param);
        Observable<String> observable = apiService.postData(api, requestBody);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }

    // Post請(qǐng)求(包含數(shù)組)
    @Override
    public void mHttpPost(Context context, String api, TreeMap map, String[] data, int type, HttpRequestCallback callBack) {
        if (mIsAddCommonParams) {
            HttpTool.getTreeCrc(map);
        }
        Observable<String> observable = apiService.postData(api, map, data);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }

    // 單文件上傳
    @Override
    public void mHttpFile(Context context, String api, File file, String fileKey, TreeMap map, int type, HttpRequestCallback callBack) {
        // 生成單個(gè)文件
        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part body = MultipartBody.Part.createFormData(fileKey, file.getName(), requestFile);

        if (mIsAddCommonParams) {
            HttpTool.getTreeCrc(map);
        }
        Map<String, RequestBody> mapValue = new HashMap<>();
        for (Object key : map.keySet()) {
            mapValue.put(key.toString(), HttpTool.convertToRequestBody(map.get(key).toString()));
        }
        Observable<String> observable = apiService.upload(api, mapValue, body);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }

    // 多文件上傳
    @Override
    public void mHttpMultiFile(Context context, String api, List<File> list, List<String> listFileName, TreeMap map, int type, HttpRequestCallback callBack) {
        //生成多個(gè)文件并添加到集合中
        List<MultipartBody.Part> params = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), list.get(i));
            MultipartBody.Part body = MultipartBody.Part.createFormData(listFileName.get(i), list.get(i).getName(), requestFile);
            params.add(body);
        }
        if (mIsAddCommonParams) {
            HttpTool.getTreeCrc(map);
        }
        Map<String, RequestBody> mapValue = new HashMap<>();
        for (Object key : map.keySet()) {
            mapValue.put(key.toString(), HttpTool.convertToRequestBody(Objects.requireNonNull(map.get(key)).toString()));
        }
        // 發(fā)送異步請(qǐng)求
        Observable<String> observable = apiService.uploadMultipart(api, mapValue, params);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }
}

當(dāng)然,在基本流程之外還有一些輔助類:

請(qǐng)求接口回調(diào)愧捕,用于應(yīng)用層對(duì)返回結(jié)果做出對(duì)應(yīng)的展示

/**
 * author: zhoufan
 * data: 2021/7/27 16:30
 * content: 請(qǐng)求接口回調(diào)
 */
public interface HttpRequestCallback {

    void onRequestNetFail(int type);

    void onRequestSuccess(String result, int type);

    void onRequestFail(String value, String failCode, int type);

}

網(wǎng)絡(luò)框架封裝的常量類奢驯,比如baseUrl等等的位置

/**
 * author: zhoufan
 * data: 2021/7/27 14:56
 * content: 網(wǎng)絡(luò)請(qǐng)求的常量類
 */
public class HttpRequestConstants {

    public static final String BASE_URL = "http://www.5mins-sun.com:8081/";
    public static final String SECRET = "5af012069a2fc4.49876009";
    public static final String APP_KEY = "5af012069a2fa";

    public static final String SIGN_METHOD_MD5 = "MD5";
    public static final String SIGN_METHOD_HMAC = "HMAC";
    public static final String CHARSET_UTF8 = "UTF-8";

    public static final String TAG = "retrofit";
}

使用Retrofit作為我們的底層網(wǎng)絡(luò)請(qǐng)求

/**
 * author: zhoufan
 * data: 2021/7/27 15:09
 * content: 對(duì)外暴露方法供外面調(diào)用
 */
public class HttpRetrofit {

    // 設(shè)置數(shù)據(jù)讀取超時(shí)時(shí)間
    public HttpRetrofit setTimeOutTime(long timeout) {
        OkHttpClientUtil.getOkHttpUtil().setTimeOutTime(timeout);
        return this;
    }

    // 設(shè)置網(wǎng)絡(luò)連接超時(shí)時(shí)間
    public HttpRetrofit setConnectTime(long timeout) {
        OkHttpClientUtil.getOkHttpUtil().setConnectTime(timeout);
        return this;
    }

    // 設(shè)置寫(xiě)入服務(wù)器的超時(shí)時(shí)間
    public HttpRetrofit setWriteTime(long timeout) {
        OkHttpClientUtil.getOkHttpUtil().setWriteTime(timeout);
        return this;
    }

    // 設(shè)置攔截器
    public HttpRetrofit setIntercept(boolean isIntercept) {
        OkHttpClientUtil.getOkHttpUtil().setIntercept(isIntercept);
        return this;
    }

    // 設(shè)置BaseUrl
    public HttpRetrofit setBaseUrl(String baseUrl) {
        RetrofitClientUtil.getRetrofitUtil().setBaseUrl(baseUrl);
        return this;
    }

    // 設(shè)置數(shù)據(jù)解析
    public HttpRetrofit addConverterFactory(Converter.Factory factory) {
        RetrofitClientUtil.getRetrofitUtil().addConverterFactory(factory);
        return this;
    }

    /**
     * 生成請(qǐng)求接口的實(shí)例
     * @return ApiService
     */
    public ApiService getApiManager(){
        return RetrofitClientUtil.getRetrofitUtil().setOkHttpClient(OkHttpClientUtil.getOkHttpUtil().build()).build().create(ApiService.class);
    }
}

然后在我們的Application里面進(jìn)行初始化設(shè)置

public class IApplication extends Application {

    private static Context mContext;

    public static Context getContext() {
        return mContext;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
        HttpUtils.getInstance().httpRequest(HttpRetrofitRequest.getInstances());
    }
}

網(wǎng)絡(luò)請(qǐng)求框架的工具類

/**
 * Created by zhoufan on 2018/1/3.
 * 工具類 (添加通用參數(shù)、加密等等)
 */
public class HttpTool {

    public static RequestBody convertToRequestBody(String param) {
        RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), param);
        return requestBody;
    }

    public static String signTopRequest(Map<String, Object> params, String secret, String signMethod) throws IOException {
        // 第一步:檢查參數(shù)是否已經(jīng)排序
        String[] keys = params.keySet().toArray(new String[0]);
        Arrays.sort(keys);

        // 第二步:把所有參數(shù)名和參數(shù)值串在一起
        StringBuilder query = new StringBuilder();
        for (String key : keys) {
            Object value = params.get(key);
            if (value instanceof String) {
                if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty((String) value)) {
                    query.append(key).append(value);
                }
            } else if (value instanceof File) {
                String strVal = getFileContent((File) value);
                query.append(key).append(strVal);
                params.remove(key);
            }
        }

        // 第三步:使用MD5/HMAC加密
        byte[] bytes;
        if (HttpRequestConstants.SIGN_METHOD_HMAC.equals(signMethod)) {
            bytes = encryptHMAC(query.toString(), secret);
        } else {
            bytes = encryptMD5(query.toString() + secret);
        }

        // 第四步:把二進(jìn)制轉(zhuǎn)化為大寫(xiě)的十六進(jìn)制
        return byte2hex(bytes);
    }

    public static byte[] encryptHMAC(String data, String secret) throws IOException {
        byte[] bytes = null;
        try {
            SecretKey secretKey = new SecretKeySpec(secret.getBytes(HttpRequestConstants.CHARSET_UTF8), "HmacMD5");
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            bytes = mac.doFinal(data.getBytes(HttpRequestConstants.CHARSET_UTF8));
        } catch (GeneralSecurityException gse) {
            throw new IOException(gse.toString());
        }
        return bytes;
    }

    public static byte[] encryptMD5(String data) throws IOException {
        byte[] md5Byte = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");  // 創(chuàng)建一個(gè)md5算法對(duì)象
            byte[] messageByte = data.getBytes("UTF-8");
            md5Byte = md.digest(messageByte);              // 獲得MD5字節(jié)數(shù)組,16*8=128位
        } catch (Exception e) {
            e.printStackTrace();
        }
        return md5Byte;
    }

    public static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toLowerCase());
        }
        return sign.toString();
    }

    public static TreeMap getTreeCrc(TreeMap maps) {
        try {
            maps.put("app_key", HttpRequestConstants.APP_KEY);
            maps.put("sign_method", "md5");
            maps.put("format", "json");
            maps.put("timestamp", timeStamp2Date());
            maps.put("sign", signTopRequest(maps, HttpRequestConstants.SECRET, HttpRequestConstants.SIGN_METHOD_MD5));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return maps;
    }

    // 時(shí)間戳轉(zhuǎn)換
    private static String timeStamp2Date() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));//設(shè)置TimeZone為上海時(shí)間
        Date now = new Date();//獲取本地時(shí)間
        try {
            now = sdf.parse(sdf.format(now));//將本地時(shí)間轉(zhuǎn)換為轉(zhuǎn)換時(shí)間為東八區(qū)
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return sdf.format(now);
    }

    // 檢測(cè)是否有網(wǎng)絡(luò)
    @SuppressLint("MissingPermission")
    public static boolean hasNetwork(Context mContext) {
        // 得到連接管理器對(duì)象
        ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
            if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) {
                return true;
            } else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) {
                return true;
            }
        } else {
            return false;
        }
        return false;
    }


    // 將文件進(jìn)行SHA1加密
    public static String getFileContent(File file) {
        try {
            StringBuffer sb = new StringBuffer();
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            FileInputStream fin = new FileInputStream(file);
            int len = -1;
            byte[] buffer = new byte[1024];//設(shè)置輸入流的緩存大小 字節(jié)
            //將整個(gè)文件全部讀入到加密器中
            while ((len = fin.read(buffer)) != -1) {
                digest.update(buffer, 0, len);
            }
            //對(duì)讀入的數(shù)據(jù)進(jìn)行加密
            byte[] bytes = digest.digest();
            for (byte b : bytes) {
                // 數(shù)byte 類型轉(zhuǎn)換為無(wú)符號(hào)的整數(shù)
                int n = b & 0XFF;
                // 將整數(shù)轉(zhuǎn)換為16進(jìn)制
                String s = Integer.toHexString(n);
                // 如果16進(jìn)制字符串是一位次绘,那么前面補(bǔ)0
                if (s.length() == 1) {
                    sb.append("0" + s);
                } else {
                    sb.append(s);
                }
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 2 * 判斷字符串是否可以轉(zhuǎn)化為json對(duì)象
     * 3 * @param content
     * 4 * @return
     * 5
     */
    public static boolean isJsonObject(String content) {
        if (content == null || TextUtils.isEmpty(content))
            return false;
        try {
            JSONObject jsonObject = new JSONObject(content);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

最終對(duì)外暴露的類

public class HttpUtils {

    private static IHttpRequest mHttpRequest;

    /**
     * 生成唯一實(shí)例
     */
    private static volatile HttpUtils mInstance;

    public static HttpUtils getInstance(){
        if (mInstance==null){
            synchronized (HttpUtils.class){
                if (mInstance==null){
                    mInstance = new HttpUtils();
                }
            }
        }
        return mInstance;
    }

    /**
     * 初始化決定使用哪一套網(wǎng)絡(luò)請(qǐng)求框架
     * @param httpRequest  默認(rèn)為OkHttp + retrofit + rxJava
     */
    public static void setInitHttpRequest(IHttpRequest httpRequest){
        mHttpRequest = httpRequest;
    }

    /**
     * 更換使用的框架
     * @param httpRequest  重新設(shè)置的網(wǎng)絡(luò)框架
     * @return
     */
    public HttpUtils httpRequest(IHttpRequest httpRequest){
        mHttpRequest = httpRequest;
        return this;
    }

    /**
     * Get請(qǐng)求(使用Path形式)
     * @param context   上下文
     * @param url       請(qǐng)求的url地址瘪阁,不使用baseUrl
     * @param type      請(qǐng)求的標(biāo)識(shí)
     * @param callBack  結(jié)果返回接口
     */
    public void executePathGet(Context context, String url, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpGetPath(context,url,type,callBack);
    }

    /**
     * GET請(qǐng)求(無(wú)參)
     * @param context     上下文
     * @param api         請(qǐng)求的api
     * @param type        請(qǐng)求的標(biāo)識(shí)
     * @param callBack    結(jié)果返回接口
     */
    public void executeGet(Context context, String api, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpGet(context,api,type,callBack);
    }

    /**
     * Get請(qǐng)求(帶參)
     * @param context    上下文
     * @param api        請(qǐng)求的api
     * @param map        請(qǐng)求的參數(shù)
     * @param type       請(qǐng)求的標(biāo)識(shí)
     * @param callBack   結(jié)果返回接口
     */
    public void executeGet(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpGet(context,api,map,type,callBack);
    }

    /**
     * Post請(qǐng)求(無(wú)參)
     * @param context    上下文
     * @param api        請(qǐng)求的api
     * @param type       請(qǐng)求的標(biāo)識(shí)
     * @param callBack   結(jié)果返回接口
     */
    public void executePost(Context context, String api, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpPost(context,api,type,callBack);
    }

    /**
     * Post請(qǐng)求(帶參)
     * 以RequestBody方式提交
     * @param context         上下文
     * @param api             請(qǐng)求的api
     * @param map             請(qǐng)求的參數(shù)
     * @param type            請(qǐng)求的標(biāo)識(shí)
     * @param callBack        結(jié)果返回接口
     */
    public void executePost(Context context, String api, TreeMap map, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpPost(context,api,map,type,callBack);
    }

    /**
     * Post請(qǐng)求(包含數(shù)組)
     * @param context    上下文
     * @param api        請(qǐng)求的api
     * @param treeMap    請(qǐng)求的參數(shù)
     * @param data       請(qǐng)求的數(shù)組
     * @param type       請(qǐng)求的標(biāo)識(shí)
     * @param callBack   結(jié)果返回接口
     */
    public void executePost(Context context, String api, TreeMap treeMap, String[] data, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpPost(context,api,treeMap,data,type,callBack);
    }

    /**
     * 單文件上傳
     * @param context    上下文
     * @param api        請(qǐng)求的api
     * @param file       上傳的文件
     * @param fileKey    上傳的文件對(duì)應(yīng)的key
     * @param map        請(qǐng)求的參數(shù)
     * @param type       請(qǐng)求的標(biāo)識(shí)
     * @param callBack   結(jié)果返回接口
     */
    public void executeFile(Context context, String api, File file, String fileKey, TreeMap map, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpFile(context, api, file, fileKey, map, type, callBack);
    }

    /**
     * 多文件上傳
     * @param context     上下文
     * @param api         請(qǐng)求的api
     * @param list        上傳的文件集合
     * @param fileList    上傳的文件集合對(duì)應(yīng)的key
     * @param map         請(qǐng)求的參數(shù)
     * @param type        請(qǐng)求的標(biāo)識(shí)
     * @param callBack    結(jié)果返回接口
     */
    public void executeMultiFile(Context context, String api, List<File> list, List<String> fileList, TreeMap map, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpMultiFile(context, api, list, fileList, map, type, callBack);
    }
}

請(qǐng)求方式的通用接口

/**
 * author: zhoufan
 * data: 2021/7/27 16:29
 * content: 請(qǐng)求接口的方式
 */
public interface IHttpRequest {

    // Get請(qǐng)求(使用Path形式)
    void mHttpGetPath(Context context, String url, int type, HttpRequestCallback mCallBack);

    // GET請(qǐng)求(無(wú)參)
    void mHttpGet(Context context, String api, int type, HttpRequestCallback mCallBack);

    // Get請(qǐng)求(帶參)
    void mHttpGet(Context context, String api, TreeMap map, int type, HttpRequestCallback mCallBack);

    // Post請(qǐng)求(無(wú)參)
    void mHttpPost(Context context, String api, int type, HttpRequestCallback mCallBack);

    // Post請(qǐng)求(帶參)
    // 以RequestBody方式提交
    void mHttpPost(Context context, String api, TreeMap map, int type, HttpRequestCallback mCallBack);

    // Post請(qǐng)求(包含數(shù)組)
    void mHttpPost(Context context, String api, TreeMap treeMap, String[] data, int type, HttpRequestCallback mCallBack);

    // 單文件上傳
    void mHttpFile(Context context, String api, File file, String fileKey, TreeMap map, int type, HttpRequestCallback mCallBack);

    // 多文件上傳
    void mHttpMultiFile(Context context, String api, List<File> list, List<String> fileList, TreeMap map, int type, HttpRequestCallback mCallBack);
}

完整的目錄結(jié)構(gòu)為:
-- ApiService
-- HttpRequestCallback
-- HttpRequestConstants
-- HttpRequestInterceptor
-- HttpRetrofit
-- HttpRetrofitRequest
-- HttpTool
-- HttpUtils
-- IHttpRequest
-- OkHttpClientUtil
-- RetrofitClientUtil
-- RetryFunction
-- RxJavaObserver

當(dāng)然撒遣,別忘記在清單文件里面設(shè)置網(wǎng)絡(luò)權(quán)限哦

<uses-permission android:name="android.permission.INTERNET" />

最后需要注意的是,千萬(wàn)別導(dǎo)入錯(cuò)誤的包管跺,我引入的依賴為

 // okHttp網(wǎng)絡(luò)請(qǐng)求框架
    // define a BOM and its version
    api(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))

    // define any required OkHttp artifacts without version
    api("com.squareup.okhttp3:okhttp")
    api("com.squareup.okhttp3:logging-interceptor")

    //retrofit網(wǎng)絡(luò)請(qǐng)求框架
    api 'com.squareup.retrofit2:retrofit:2.9.0'
    //retrofit添加Json解析返回?cái)?shù)據(jù)
    api 'com.squareup.retrofit2:converter-scalars:2.9.0'
    api 'com.squareup.retrofit2:converter-gson:2.9.0'
    //retrofit添加RxJava支持
    api 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'

    api "io.reactivex.rxjava3:rxjava:3.0.13"
    api 'io.reactivex.rxjava3:rxandroid:3.0.0'

最后看下效果

/**
 * 網(wǎng)絡(luò)測(cè)試類
 */
class NetWorkActivity : AppCompatActivity(), HttpRequestCallback {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_net_work)
    }

    // Get請(qǐng)求(使用Path形式)
    fun requestOne(view: View) {
        HttpUtils.getInstance().executePathGet(this, "http://www.baidu.com/", 0, this)
    }

    // Post請(qǐng)求(帶參以RequestBody方式提交)
    fun requestFive(view: View) {
        val treeMap = TreeMap<String,Any>()
        HttpUtils.getInstance().executePost(this, "/app/get_version_info", treeMap,0, this)
    }

    // 網(wǎng)絡(luò)連接失敗
    override fun onRequestNetFail(type: Int) {
    }

    // 接口請(qǐng)求成功
    override fun onRequestSuccess(result: String?, type: Int) {
        textView.text = result
    }

    // 接口請(qǐng)求失敗
    override fun onRequestFail(value: String?, failCode: String?, type: Int) {
    }
}
網(wǎng)絡(luò)請(qǐng)求.gif

在這里我只測(cè)試了Get請(qǐng)求(使用Path形式)和Post請(qǐng)求(帶參以RequestBody方式提交)义黎,其他的大家可以根據(jù)自己的需求自行測(cè)試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末豁跑,一起剝皮案震驚了整個(gè)濱河市廉涕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艇拍,老刑警劉巖火的,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異淑倾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)征椒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)娇哆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人勃救,你說(shuō)我怎么就攤上這事碍讨。” “怎么了蒙秒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵勃黍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我晕讲,道長(zhǎng)覆获,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任瓢省,我火速辦了婚禮弄息,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勤婚。我一直安慰自己摹量,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布馒胆。 她就那樣靜靜地躺著缨称,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祝迂。 梳的紋絲不亂的頭發(fā)上睦尽,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音液兽,去河邊找鬼骂删。 笑死掌动,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宁玫。 我是一名探鬼主播粗恢,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼欧瘪!你這毒婦竟也來(lái)了眷射?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤佛掖,失蹤者是張志新(化名)和其女友劉穎妖碉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體芥被,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡欧宜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拴魄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冗茸。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匹中,靈堂內(nèi)的尸體忽然破棺而出夏漱,到底是詐尸還是另有隱情,我是刑警寧澤顶捷,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布挂绰,位于F島的核電站,受9級(jí)特大地震影響服赎,放射性物質(zhì)發(fā)生泄漏葵蒂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一专肪、第九天 我趴在偏房一處隱蔽的房頂上張望刹勃。 院中可真熱鬧,春花似錦嚎尤、人聲如沸荔仁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乏梁。三九已至,卻和暖如春关贵,著一層夾襖步出監(jiān)牢的瞬間遇骑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工揖曾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留落萎,地道東北人计雌。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓嘿般,卻偏偏與公主長(zhǎng)得像创倔,于是被迫代替她去往敵國(guó)和親瘩扼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容