Android网络篇(四)—— 自己动手封装一个属于自己的网络请求框架

网络请求框架算是android体系当中一个比较重要的部分,在android历史中关于网络的演进也经历了几个阶段,到目前为止,比较通用的网络请求框架就是OkHttp + Retrofit +RxJava+Gson,当然,关于这个组合使用网上也有很多,但是,那个毕竟是别人的东西,大部分时候只有适合自己的才是最好的,所以,自己封装一个网络请求框架就显得比较重要了,废话不多说,直接动手开干。

网络请求框架封装大概流程如下:

(1)封装OkHttp实例
(2)封装OkHttp的拦截请求
(3)封装Retrofit实例
(4)封装Retrofit的注解通用类
(5)增加请求失败重试功能
(6)封装返回结果
(7)对上述封装进行组装形成一个完整的框架

第一步:封装OkHttp实例

public class OkHttpClientUtil {

    // 是否开启拦截,默认情况下开启
    private boolean mIsIntercept = true;
    // 设置数据读取超时时间
    private long mReadTimeOut = 20000;
    // 设置网络连接超时时间
    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;
    }

    /**
     * 私有构造函数,保证全局唯一
     */
    private OkHttpClientUtil(){

    }

    // 设置数据读取超时时间
    OkHttpClientUtil setTimeOutTime(long timeout) {
        mReadTimeOut = timeout;
        return this;
    }

    // 设置网络连接超时时间
    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);
        // 默认开启请求的打印信息数据,在每次发布版本的时候可以手动关闭
        if (mIsIntercept) {
            okHttpClient.addInterceptor(new HttpRequestInterceptor());
        }
        return okHttpClient.build();
    }
}

第二步:封装OkHttp的拦截请求

/**
 * author: zhoufan
 * data: 2021/7/27 14:39
 * content: 拦截每次发出的请求并打印出来
 */
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", "请求参数: | " + params);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第三步:封装Retrofit实例

/**
 * author: zhoufan
 * data: 2021/7/27 14:45
 * content: 对Retrofit进行封装
 */
public class RetrofitClientUtil {

    // 网络请求的baseUrl
    private String mBaseUrl;
    // 设置数据解析器
    private Converter.Factory mBaseFactory;
    // 新增数据解析器
    private Converter.Factory mAddFactory;
    // 设置网络请求适配器
    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;
        // 默认基础数据类型的解析
        mBaseFactory = ScalarsConverterFactory.create();
        // RxJava来处理Call返回值
        mCallFactory = RxJava3CallAdapterFactory.create();
    }

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

    // 设置数据解析
    public RetrofitClientUtil addConverterFactory(Converter.Factory factory) {
        this.mAddFactory = factory;
        return this;
    }

    // 设置网络请求适配器
    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请求(带数组)
    @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<?>> {

    // 可重试次数
    private int maxConnectCount = 3;
    // 当前已重试次数
    private int currentRetryCount = 0;
    // 重试等待时间
    private int waitRetryTime = 0;

    @Override
    public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Throwable {
        // 参数Observable<Throwable>中的泛型 = 上游操作符抛出的异常,可通过该条件来判断异常的类型
        return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {

                // 输出异常信息
                LogUtil.d(HttpRequestConstants.TAG, "发生异常 = " + throwable.toString());

                /**
                 * 需求1:根据异常类型选择是否重试
                 * 即,当发生的异常 = 网络异常 = IO异常 才选择重试
                 */
                if (throwable instanceof IOException) {
                    Log.d(HttpRequestConstants.TAG, "属于IO异常,需重试");

                    /**
                     * 需求2:限制重试次数
                     * 即,当已重试次数 < 设置的重试次数,才选择重试
                     */
                    if (currentRetryCount < maxConnectCount) {

                        // 记录重试次数
                        currentRetryCount++;
                        Log.d(HttpRequestConstants.TAG, "重试次数 = " + currentRetryCount);

                        /**
                         * 需求2:实现重试
                         * 通过返回的Observable发送的事件 = Next事件,从而使得retryWhen()重订阅,最终实现重试功能
                         *
                         * 需求3:延迟1段时间再重试
                         * 采用delay操作符 = 延迟一段时间发送,以实现重试间隔设置
                         *
                         * 需求4:遇到的异常越多,时间越长
                         * 在delay操作符的等待时间内设置 = 每重试1次,增多延迟重试时间1s
                         */
                        // 设置等待时间
                        waitRetryTime = 1000 + currentRetryCount * 1000;
                        Log.d(HttpRequestConstants.TAG, "等待时间 =" + waitRetryTime);
                        return Observable.just(1).delay(waitRetryTime, TimeUnit.MILLISECONDS);
                    } else {
                        // 若重试次数已 > 设置重试次数,则不重试
                        // 通过发送error来停止重试(可在观察者的onError()中获取信息)
                        return Observable.error(new Throwable("重试次数已超过设置次数 = " + currentRetryCount + ",即 不再重试"));
                    }
                }
                // 若发生的异常不属于I/O异常,则不重试
                // 通过返回的Observable发送的事件 = Error事件 实现(可在观察者的onError()中获取信息)
                else {
                    return Observable.error(new Throwable("发生了非网络异常(非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.检查网络设置
        if (!HttpTool.hasNetwork(mContext)) {
            MyToast.showCenterSortToast(mContext, mContext.getResources().getString(R.string.connect_error));
            onComplete();
            mCallBack.onRequestNetFail(mType);
            return;
        }
        // 2.非网络错误,接口请求错误
        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;
    // 是否添加通用参数
    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();
    }

    /**
     * 设置通用参数
     */
    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请求(包含数组)
    @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()));
        }
        // 发送异步请求
        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);

}

网络框架封装的常量类,比如baseUrl等等的位置

/**
 * author: zhoufan
 * data: 2021/7/27 14:56
 * content: 网络请求的常量类
 */
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作为我们的底层网络请求

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

    // 设置数据读取超时时间
    public HttpRetrofit setTimeOutTime(long timeout) {
        OkHttpClientUtil.getOkHttpUtil().setTimeOutTime(timeout);
        return this;
    }

    // 设置网络连接超时时间
    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;
    }

    // 设置数据解析
    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());
    }
}

网络请求框架的工具类

/**
 * Created by zhoufan on 2018/1/3.
 * 工具类 (添加通用参数、加密等等)
 */
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 {
        // 第一步:检查参数是否已经排序
        String[] keys = params.keySet().toArray(new String[0]);
        Arrays.sort(keys);

        // 第二步:把所有参数名和参数值串在一起
        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");  // 创建一个md5算法对象
            byte[] messageByte = data.getBytes("UTF-8");
            md5Byte = md.digest(messageByte);              // 获得MD5字节数组,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));//将本地时间转换为转换时间为东八区
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return sdf.format(now);
    }

    // 检测是否有网络
    @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];//设置输入流的缓存大小 字节
            //将整个文件全部读入到加密器中
            while ((len = fin.read(buffer)) != -1) {
                digest.update(buffer, 0, len);
            }
            //对读入的数据进行加密
            byte[] bytes = digest.digest();
            for (byte b : bytes) {
                // 数byte 类型转换为无符号的整数
                int n = b & 0XFF;
                // 将整数转换为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;
    }

    /**
     * 初始化决定使用哪一套网络请求框架
     * @param httpRequest  默认为OkHttp + retrofit + rxJava
     */
    public static void setInitHttpRequest(IHttpRequest httpRequest){
        mHttpRequest = httpRequest;
    }

    /**
     * 更换使用的框架
     * @param httpRequest  重新设置的网络框架
     * @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        请求的参数
     * @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             请求的参数
     * @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请求(包含数组)
     * @param context    上下文
     * @param api        请求的api
     * @param treeMap    请求的参数
     * @param data       请求的数组
     * @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        请求的参数
     * @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         请求的参数
     * @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请求(包含数组)
    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

当然,别忘记在清单文件里面设置网络权限哦

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

最后需要注意的是,千万别导入错误的包,我引入的依赖为

 // okHttp网络请求框架
    // 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网络请求框架
    api 'com.squareup.retrofit2:retrofit:2.9.0'
    //retrofit添加Json解析返回数据
    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'

最后看下效果

/**
 * 网络测试类
 */
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)
    }

    // 网络连接失败
    override fun onRequestNetFail(type: Int) {
    }

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

    // 接口请求失败
    override fun onRequestFail(value: String?, failCode: String?, type: Int) {
    }
}
网络请求.gif

在这里我只测试了Get请求(使用Path形式)和Post请求(带参以RequestBody方式提交),其他的大家可以根据自己的需求自行测试。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容