Android網(wǎng)絡篇(四)—— 自己動手封裝一個屬于自己的網(wǎng)絡請求框架

網(wǎng)絡請求框架算是android體系當中一個比較重要的部分,在android歷史中關于網(wǎng)絡的演進也經(jīng)歷了幾個階段,到目前為止,比較通用的網(wǎng)絡請求框架就是OkHttp + Retrofit +RxJava+Gson,當然,關于這個組合使用網(wǎng)上也有很多,但是,那個畢竟是別人的東西,大部分時候只有適合自己的才是最好的,所以,自己封裝一個網(wǎng)絡請求框架就顯得比較重要了,廢話不多說,直接動手開干。

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

(1)封裝OkHttp實例
(2)封裝OkHttp的攔截請求
(3)封裝Retrofit實例
(4)封裝Retrofit的注解通用類
(5)增加請求失敗重試功能
(6)封裝返回結果
(7)對上述封裝進行組裝形成一個完整的框架

第一步:封裝OkHttp實例

public class OkHttpClientUtil {

    // 是否開啟攔截,默認情況下開啟
    private boolean mIsIntercept = true;
    // 設置數(shù)據(jù)讀取超時時間
    private long mReadTimeOut = 20000;
    // 設置網(wǎng)絡連接超時時間
    private long mConnectTimeOut = 20000;
    // 設置寫入服務器的超時時間
    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;
    }

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

    }

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

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

    // 設置寫入服務器的超時時間
    OkHttpClientUtil setWriteTime(long timeout) {
        mWriteTimeOut = timeout;
        return this;
    }

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

    // 設置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);
        // 默認開啟請求的打印信息數(shù)據(jù),在每次發(fā)布版本的時候可以手動關閉
        if (mIsIntercept) {
            okHttpClient.addInterceptor(new HttpRequestInterceptor());
        }
        return okHttpClient.build();
    }
}

第二步:封裝OkHttp的攔截請求

/**
 * author: zhoufan
 * data: 2021/7/27 14:39
 * content: 攔截每次發(fā)出的請求并打印出來
 */
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", "請求地址:| " + request.toString());
        if (request.body() != null) {
            printParams(request.body());
        }
        Log.d("request", "請求體返回:| Response:" + content);
        Log.d("request", "----------請求耗時:" + 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", "請求參數(shù): | " + params);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第三步:封裝Retrofit實例

/**
 * author: zhoufan
 * data: 2021/7/27 14:45
 * content: 對Retrofit進行封裝
 */
public class RetrofitClientUtil {

    // 網(wǎng)絡請求的baseUrl
    private String mBaseUrl;
    // 設置數(shù)據(jù)解析器
    private Converter.Factory mBaseFactory;
    // 新增數(shù)據(jù)解析器
    private Converter.Factory mAddFactory;
    // 設置網(wǎng)絡請求適配器
    private CallAdapter.Factory mCallFactory;
    // 設置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;
        // 默認基礎數(shù)據(jù)類型的解析
        mBaseFactory = ScalarsConverterFactory.create();
        // RxJava來處理Call返回值
        mCallFactory = RxJava3CallAdapterFactory.create();
    }

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

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

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

    // 設置寫入服務器的超時時間
    public RetrofitClientUtil setOkHttpClient(OkHttpClient okHttpClient) {
        this.mOkHttpClient = okHttpClient;
        return this;
    }

    // 設置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請求(無參) api = news
    // http://www.5mins-sun.com:8081/news
    @GET("{api}")
    Observable<String> getData(@Path("api") String api);

    // GET請求(帶參) 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請求(無參)
    @POST("{api}")
    Observable<String> postData(@Path(value = "api", encoded = true) String api);

    // POST請求,以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請求,以表單方式提交
    // 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請求(帶數(shù)組)
    @FormUrlEncoded
    @POST("{api}")
    Observable<ResponseBody> postData(@Path("api") String api, @FieldMap Map<String, String> maps, @Query("meta[]") String... linked);

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

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

}

第五步:增加請求失敗重試功能

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

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

    @Override
    public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Throwable {
        // 參數(shù)Observable<Throwable>中的泛型 = 上游操作符拋出的異常,可通過該條件來判斷異常的類型
        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ù)異常類型選擇是否重試
                 * 即,當發(fā)生的異常 = 網(wǎng)絡異常 = IO異常 才選擇重試
                 */
                if (throwable instanceof IOException) {
                    Log.d(HttpRequestConstants.TAG, "屬于IO異常,需重試");

                    /**
                     * 需求2:限制重試次數(shù)
                     * 即,當已重試次數(shù) < 設置的重試次數(shù),才選擇重試
                     */
                    if (currentRetryCount < maxConnectCount) {

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

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

第六步:封裝返回結果

/**
 * author: zhoufan
 * data: 2021/7/27 16:59
 * content: 處理接口返回的請求結果
 */
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) {

    }

    /**
     * 接口請求成功
     */
    @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();
        }
    }

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

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

    }
}

第七步:對上述封裝進行組裝形成一個完整的框架

/**
 * author: zhoufan
 * data: 2021/7/27 16:41
 * content: 對應用層暴露的調用類
 */
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ù)
     */
    public void isAddCommonParams(boolean isAddCommonParams) {
        this.mIsAddCommonParams = isAddCommonParams;
    }

    // Get請求(使用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請求(無參)
    @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請求(帶參)
    @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請求(無參)
    @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請求(帶參)
    // 以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請求(包含數(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) {
        // 生成單個文件
        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) {
        //生成多個文件并添加到集合中
        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ā)送異步請求
        Observable<String> observable = apiService.uploadMultipart(api, mapValue, params);
        observable.retryWhen(new RetryFunction()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new RxJavaObserver(context, type, callBack));
    }
}

當然,在基本流程之外還有一些輔助類:

請求接口回調,用于應用層對返回結果做出對應的展示

/**
 * author: zhoufan
 * data: 2021/7/27 16:30
 * content: 請求接口回調
 */
public interface HttpRequestCallback {

    void onRequestNetFail(int type);

    void onRequestSuccess(String result, int type);

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

}

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

/**
 * author: zhoufan
 * data: 2021/7/27 14:56
 * content: 網(wǎ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)絡請求

/**
 * author: zhoufan
 * data: 2021/7/27 15:09
 * content: 對外暴露方法供外面調用
 */
public class HttpRetrofit {

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

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

    // 設置寫入服務器的超時時間
    public HttpRetrofit setWriteTime(long timeout) {
        OkHttpClientUtil.getOkHttpUtil().setWriteTime(timeout);
        return this;
    }

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

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

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

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

然后在我們的Application里面進行初始化設置

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)絡請求框架的工具類

/**
 * 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);
        }

        // 第四步:把二進制轉化為大寫的十六進制
        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)建一個md5算法對象
            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;
    }

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

    // 檢測是否有網(wǎng)絡
    @SuppressLint("MissingPermission")
    public static boolean hasNetwork(Context mContext) {
        // 得到連接管理器對象
        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;
    }


    // 將文件進行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];//設置輸入流的緩存大小 字節(jié)
            //將整個文件全部讀入到加密器中
            while ((len = fin.read(buffer)) != -1) {
                digest.update(buffer, 0, len);
            }
            //對讀入的數(shù)據(jù)進行加密
            byte[] bytes = digest.digest();
            for (byte b : bytes) {
                // 數(shù)byte 類型轉換為無符號的整數(shù)
                int n = b & 0XFF;
                // 將整數(shù)轉換為16進制
                String s = Integer.toHexString(n);
                // 如果16進制字符串是一位,那么前面補0
                if (s.length() == 1) {
                    sb.append("0" + s);
                } else {
                    sb.append(s);
                }
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 2 * 判斷字符串是否可以轉化為json對象
     * 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;
        }
    }
}

最終對外暴露的類

public class HttpUtils {

    private static IHttpRequest mHttpRequest;

    /**
     * 生成唯一實例
     */
    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)絡請求框架
     * @param httpRequest  默認為OkHttp + retrofit + rxJava
     */
    public static void setInitHttpRequest(IHttpRequest httpRequest){
        mHttpRequest = httpRequest;
    }

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

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

    /**
     * GET請求(無參)
     * @param context     上下文
     * @param api         請求的api
     * @param type        請求的標識
     * @param callBack    結果返回接口
     */
    public void executeGet(Context context, String api, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpGet(context,api,type,callBack);
    }

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

    /**
     * Post請求(無參)
     * @param context    上下文
     * @param api        請求的api
     * @param type       請求的標識
     * @param callBack   結果返回接口
     */
    public void executePost(Context context, String api, int type, HttpRequestCallback callBack){
        mHttpRequest.mHttpPost(context,api,type,callBack);
    }

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

    /**
     * Post請求(包含數(shù)組)
     * @param context    上下文
     * @param api        請求的api
     * @param treeMap    請求的參數(shù)
     * @param data       請求的數(shù)組
     * @param type       請求的標識
     * @param callBack   結果返回接口
     */
    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        請求的api
     * @param file       上傳的文件
     * @param fileKey    上傳的文件對應的key
     * @param map        請求的參數(shù)
     * @param type       請求的標識
     * @param callBack   結果返回接口
     */
    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         請求的api
     * @param list        上傳的文件集合
     * @param fileList    上傳的文件集合對應的key
     * @param map         請求的參數(shù)
     * @param type        請求的標識
     * @param callBack    結果返回接口
     */
    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);
    }
}

請求方式的通用接口

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

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

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

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

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

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

    // Post請求(包含數(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);
}

完整的目錄結構為:
-- ApiService
-- HttpRequestCallback
-- HttpRequestConstants
-- HttpRequestInterceptor
-- HttpRetrofit
-- HttpRetrofitRequest
-- HttpTool
-- HttpUtils
-- IHttpRequest
-- OkHttpClientUtil
-- RetrofitClientUtil
-- RetryFunction
-- RxJavaObserver

當然,別忘記在清單文件里面設置網(wǎng)絡權限哦

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

最后需要注意的是,千萬別導入錯誤的包,我引入的依賴為

 // okHttp網(wǎ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)絡請求框架
    api 'com.squareup.retrofit2:retrofit:2.9.0'
    //retrofit添加Json解析返回數(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)絡測試類
 */
class NetWorkActivity : AppCompatActivity(), HttpRequestCallback {

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

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

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

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

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容